Skip to content

Commit

Permalink
added API security on Nuts node using asymetric keys (#100)
Browse files Browse the repository at this point in the history
* added configuration, loading and parsing of the private key for API security.
added token generation for every request.

* changed kid to jwk thumbprint instead of ssh fingerprint

* finish up node API security, set aud, iss and sub to correct values

* PR feedback

* Update config.go

---------

Co-authored-by: reinkrul <[email protected]>
  • Loading branch information
woutslakhorst and reinkrul authored Feb 23, 2023
1 parent 372232d commit 656d0f3
Show file tree
Hide file tree
Showing 10 changed files with 665 additions and 241 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ RUN npm run build
#
# Build backend
#
FROM golang:1.17-alpine as backend-builder
FROM golang:1.20-alpine as backend-builder

ARG TARGETARCH
ARG TARGETOS
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ $ docker run -p 1303:1303 nutsfoundation/nuts-registry-admin-demo
When running in Docker without a config file mounted at `/app/server.config.yaml` it will use the default configuration.
In this case the default username will be `[email protected]`. The password is generated and printed in the log on startup.

The `nutsnodeapikeyfile` config parameter should point to a PEM encoded private key file. The corresponding public key should be configured on the Nuts node in SSH authorized keys format.
`nutsnodeapiuser` Is required when using Nuts node API token security. It must match the user in the SSH authorized keys file.

## Technology Stack

Frontend framework is vue.js 3.x
Expand Down
57 changes: 51 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package main

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"golang.org/x/crypto/ssh"
"io"
"log"
"os"
Expand Down Expand Up @@ -38,13 +42,19 @@ func defaultConfig() Config {
}

type Config struct {
Credentials Credentials `koanf:"credentials"`
DBFile string `koanf:"dbfile"`
HTTPPort int `koanf:"port"`
NutsNodeAddress string `koanf:"nutsnodeaddr"`
CustomersFile string `koanf:"customersfile"`
Branding Branding `koanf:"branding"`
Credentials Credentials `koanf:"credentials"`
DBFile string `koanf:"dbfile"`
HTTPPort int `koanf:"port"`
// NutsNodeAddress contains the address of the Nuts node. It's also used in the aud field when API security is enabled
NutsNodeAddress string `koanf:"nutsnodeaddr"`
// NutsNodeAPIKeyFile points to the private key used to sign JWTs. If empty Nuts node API security is not enabled
NutsNodeAPIKeyFile string `koanf:"nutsnodeapikeyfile"`
// NutsNodeAPIUser contains the API key user that will go into the iss field. It must match the user with the public key from the authorized_keys file in the Nuts node
NutsNodeAPIUser string `koanf:"nutsnodeapiuser"`
CustomersFile string `koanf:"customersfile"`
Branding Branding `koanf:"branding"`
sessionKey *ecdsa.PrivateKey
apiKey crypto.Signer
}

type Credentials struct {
Expand Down Expand Up @@ -118,6 +128,21 @@ func loadConfig() Config {
log.Fatalf("error while unmarshalling config: %v", err)
}

// Load the API key
if len(config.NutsNodeAPIKeyFile) > 0 {
bytes, err := os.ReadFile(config.NutsNodeAPIKeyFile)
if err != nil {
log.Fatalf("error while reading private key file: %v", err)
}
config.apiKey, err = pemToPrivateKey(bytes)
if err != nil {
log.Fatalf("error while decoding private key file: %v", err)
}
if len(config.NutsNodeAPIUser) == 0 {
log.Fatal("nutsnodeapiuser config is required with nutsnodeapikeyfile")
}
}

return config
}

Expand Down Expand Up @@ -156,3 +181,23 @@ func envProvider() *env.Env {
strings.TrimPrefix(s, defaultPrefix)), "_", defaultDelimiter, -1)
})
}

// pemToPrivateKey converts a PEM encoded private key to a Signer interface. It supports EC, RSA and PKIX PEM encoded strings
func pemToPrivateKey(bytes []byte) (signer crypto.Signer, err error) {
key, _ := ssh.ParseRawPrivateKey(bytes)
if key == nil {
err = errors.New("failed to decode PEM file")
return
}

switch k := key.(type) {
case *rsa.PrivateKey:
signer = k
case *ecdsa.PrivateKey:
signer = k
default:
err = fmt.Errorf("unsupported private key type: %T", k)
}

return
}
6 changes: 3 additions & 3 deletions domain/credentials/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (s Service) ManageNutsOrgCredential(customer domain.Customer, shouldHaveCre
func (s Service) GetCredentials(customer domain.Customer) ([]domain.OrganizationConceptCredential, error) {
return s.search(vcrApi.SearchVCQuery{
Type: []ssi.URI{ssi.MustParseURI(credential.NutsOrganizationCredentialType), ssi.MustParseURI(vc.VerifiableCredentialType)},
Context: []ssi.URI{ssi.MustParseURI(vc.VCContextV1), ssi.MustParseURI(credential.NutsContext)},
Context: []ssi.URI{ssi.MustParseURI(vc.VCContextV1), ssi.MustParseURI(credential.NutsV1Context)},
CredentialSubject: []interface{}{domain.NutsOrganizationCredentialSubject{
ID: *customer.Did,
}},
Expand All @@ -98,7 +98,7 @@ func (s Service) SearchOrganizations(name, city string) ([]domain.OrganizationCo
}
return s.search(vcrApi.SearchVCQuery{
Type: []ssi.URI{ssi.MustParseURI(credential.NutsOrganizationCredentialType), ssi.MustParseURI(vc.VerifiableCredentialType)},
Context: []ssi.URI{ssi.MustParseURI(vc.VCContextV1), ssi.MustParseURI(credential.NutsContext)},
Context: []ssi.URI{ssi.MustParseURI(vc.VCContextV1), ssi.MustParseURI(credential.NutsV1Context)},
CredentialSubject: []interface{}{domain.NutsOrganizationCredentialSubject{
Organization: domain.Organization{
Name: name,
Expand Down Expand Up @@ -229,7 +229,7 @@ func (s Service) issueNutsOrgCredential(customer domain.Customer) error {
City: *customer.City,
}})

visiblity := vcrApi.IssueVCRequestVisibilityPublic
visiblity := vcrApi.Public
requestBody := vcrApi.IssueVCJSONRequestBody{
Type: "NutsOrganizationCredential",
Issuer: vendorDID.Id,
Expand Down
8 changes: 5 additions & 3 deletions domain/customers/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ func (s Service) ConnectCustomer(reqCustomer domain.Customer, serviceProviderID
controllers := []string{serviceProviderID.String()}

didDoc, err := s.VDRClient.Create(nutsApi.DIDCreateRequest{
SelfControl: &selfControl,
Controllers: &controllers,
CapabilityInvocation: &capabilityInvocation,
SelfControl: &selfControl,
Controllers: &controllers,
VerificationMethodRelationship: nutsApi.VerificationMethodRelationship{
CapabilityInvocation: &capabilityInvocation,
},
})
if err != nil {
return nil, domain.UnwrapAPIError(err)
Expand Down
158 changes: 80 additions & 78 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,161 +1,163 @@
module github.com/nuts-foundation/nuts-registry-admin-demo

go 1.17
go 1.19

require (
github.com/deepmap/oapi-codegen v1.10.1
github.com/knadh/koanf v1.4.1
github.com/labstack/echo/v4 v4.7.2
github.com/lestrrat-go/jwx v1.2.23
github.com/nuts-foundation/go-did v0.3.0
github.com/nuts-foundation/nuts-node v1.0.1-0.20220505113658-c91d8ad758f6
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/deepmap/oapi-codegen v1.12.4
github.com/knadh/koanf v1.5.0
github.com/labstack/echo/v4 v4.10.0
github.com/lestrrat-go/jwx v1.2.25
github.com/nuts-foundation/go-did v0.4.0
github.com/nuts-foundation/nuts-node v1.0.1-0.20230215083758-60527f5fff72
github.com/sirupsen/logrus v1.9.0
github.com/spf13/pflag v1.0.5
go.etcd.io/bbolt v1.3.6
go.etcd.io/bbolt v1.3.7
)

require (
github.com/Regis24GmbH/go-phonetics v1.0.0 // indirect
github.com/alexandrevicenzi/go-sse v1.6.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/avast/retry-go/v4 v4.0.4 // indirect
github.com/avast/retry-go/v4 v4.3.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bsm/redislock v0.7.1 // indirect
github.com/bsm/redislock v0.7.2 // indirect
github.com/bwesterb/byteswriter v1.0.0 // indirect
github.com/bwesterb/go-atum v1.0.0 // indirect
github.com/bwesterb/go-atum v1.1.5 // indirect
github.com/bwesterb/go-exptable v1.0.0 // indirect
github.com/bwesterb/go-pow v1.0.0 // indirect
github.com/bwesterb/go-xmssmt v1.0.0 // indirect
github.com/cbroglie/mustache v1.3.1 // indirect
github.com/bwesterb/go-xmssmt v1.5.2 // indirect
github.com/cbroglie/mustache v1.4.0 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/eknkc/basex v1.0.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/eknkc/basex v1.0.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fxamacker/cbor v1.5.0 // indirect
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7 // indirect
github.com/go-chi/chi v3.3.3+incompatible // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-redis/redis/v8 v8.8.0 // indirect
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/goccy/go-json v0.9.6 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fxamacker/cbor v1.5.1 // indirect
github.com/go-chi/chi/v5 v5.0.7 // indirect
github.com/go-co-op/gocron v1.14.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-redis/redis/v9 v9.0.0-rc.2 // indirect
github.com/go-redsync/redsync/v4 v4.7.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/goodsign/monday v1.0.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.0.0 // indirect
github.com/hashicorp/go-hclog v1.2.1 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.4.3 // indirect
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/go-version v1.2.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault/api v1.5.0 // indirect
github.com/hashicorp/vault/api v1.9.0 // indirect
github.com/hashicorp/vault/sdk v0.4.1 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jasonlvhit/gocron v0.0.0-20180312192515-54194c9749d4 // indirect
github.com/jinzhu/gorm v1.9.12 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jinzhu/gorm v1.9.16 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/klauspost/compress v1.14.4 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/lib/pq v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/lib/pq v1.10.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mdp/qrterminal/v3 v3.0.0 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/minio/sha256-simd v0.1.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mr-tron/base58 v1.1.3 // indirect
github.com/multiformats/go-multihash v0.0.11 // indirect
github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a // indirect
github.com/nats-io/nats-server/v2 v2.8.1 // indirect
github.com/nats-io/nats.go v1.14.0 // indirect
github.com/nats-io/jwt/v2 v2.3.0 // indirect
github.com/nats-io/nats-server/v2 v2.9.14 // indirect
github.com/nats-io/nats.go v1.23.0 // indirect
github.com/nats-io/nkeys v0.3.0 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 // indirect
github.com/nightlyone/lockfile v1.0.0 // indirect
github.com/nuts-foundation/crypto-ecies v0.0.0-20211207143025-5b84f9efce2b // indirect
github.com/nuts-foundation/go-leia/v2 v2.0.1 // indirect
github.com/nuts-foundation/go-leia/v3 v3.1.0 // indirect
github.com/nuts-foundation/go-leia/v3 v3.3.0 // indirect
github.com/nuts-foundation/go-stoabs v1.6.0 // indirect
github.com/ockam-network/did v0.1.4-0.20210103172416-02ae01ce06d8 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/piprate/json-gold v0.4.1 // indirect
github.com/piprate/json-gold v0.5.1-0.20230111113000-6ddbe6e6f19f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/privacybydesign/gabi v0.0.0-20210714094051-ba80a6a8c5d8 // indirect
github.com/privacybydesign/irmago v0.10.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/privacybydesign/gabi v0.0.0-20221012093643-8e978bfbb252 // indirect
github.com/privacybydesign/irmago v0.11.1 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/shengdoushi/base58 v1.0.0 // indirect
github.com/sietseringers/go-sse v0.0.0-20200801161811-e2cf2c63ca50 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/stretchr/testify v1.7.1 // indirect
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/stretchr/testify v1.8.1 // indirect
github.com/templexxx/cpu v0.0.9 // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect
github.com/tidwall/gjson v1.14.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/timshannon/bolthold v0.0.0-20190812165541-a85bcc049a2e // indirect
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a // indirect
github.com/twmb/murmur3 v1.1.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v0.19.0 // indirect
go.opentelemetry.io/otel/metric v0.19.0 // indirect
go.opentelemetry.io/otel/trace v0.19.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.46.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/term v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/Regis24GmbH/go-diacritics.v1 v1.0.0 // indirect
gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.2 // indirect
gopkg.in/Regis24GmbH/go-phonetics.v2 v2.0.2 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
schneider.vip/problem v1.6.0 // indirect
schneider.vip/problem v1.8.1 // indirect
)
Loading

0 comments on commit 656d0f3

Please sign in to comment.