From ab4206cc93b0e0063cb016521f640ca56ef48a02 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:29:55 +0530 Subject: [PATCH] NOISSUE - Vault operations with app role authentication (#2084) Signed-off-by: Arvindh Signed-off-by: arvindh123 --- certs/README.md | 77 +++---- certs/mocks/pki.go | 5 + certs/pki/vault.go | 109 +++++++++- cmd/certs/main.go | 16 +- docker/.env | 67 ++++-- docker/README.md | 15 ++ docker/addons/certs/docker-compose.yml | 9 +- docker/addons/vault/.gitignore | 1 + docker/addons/vault/README.md | 102 ++++++--- ...magistrala_things_certs_issue.template.hcl | 32 +++ docker/addons/vault/vault-set-pki.sh | 144 ------------- docker/addons/vault/vault_copy_certs.sh | 33 +++ .../{vault-init.sh => vault_copy_env.sh} | 9 +- docker/addons/vault/vault_create_approle.sh | 95 +++++++++ docker/addons/vault/vault_init.sh | 18 ++ docker/addons/vault/vault_set_pki.sh | 199 ++++++++++++++++++ .../{vault-unseal.sh => vault_unseal.sh} | 0 docker/docker-compose.yml | 18 +- docker/nginx/entrypoint.sh | 1 + docker/nginx/nginx-key.conf | 10 +- docker/nginx/nginx-x509.conf | 19 +- go.mod | 9 +- go.sum | 44 ++++ 23 files changed, 764 insertions(+), 268 deletions(-) create mode 100644 docker/addons/vault/magistrala_things_certs_issue.template.hcl delete mode 100755 docker/addons/vault/vault-set-pki.sh create mode 100755 docker/addons/vault/vault_copy_certs.sh rename docker/addons/vault/{vault-init.sh => vault_copy_env.sh} (84%) create mode 100755 docker/addons/vault/vault_create_approle.sh create mode 100755 docker/addons/vault/vault_init.sh create mode 100755 docker/addons/vault/vault_set_pki.sh rename docker/addons/vault/{vault-unseal.sh => vault_unseal.sh} (100%) diff --git a/certs/README.md b/certs/README.md index d279b93650..ad89e581c2 100644 --- a/certs/README.md +++ b/certs/README.md @@ -30,38 +30,41 @@ curl -s -S -X DELETE http://localhost:9019/certs/revoke -H "Authorization: Beare The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ------------------------- | --------------------------------------------------------------------------- | ----------------------------------- | -| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info | -| MG_CERTS_HTTP_HOST | Service Certs host | "" | -| MG_CERTS_HTTP_PORT | Service Certs port | 9019 | -| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | -| MG_AUTH_GRPC_URL | Auth service gRPC URL | | -| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | -| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" | -| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" | -| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" | -| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt | -| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key | -| MG_CERTS_VAULT_HOST | Vault host | "" | -| MG_VAULT_PKI_INT_PATH | Vault PKI intermediate path | pki_int | -| MG_VAULT_CA_ROLE_NAME | Vault PKI role name | magistrala | -| MG_VAULT_TOKEN | Vault token | "" | -| MG_CERTS_DB_HOST | Database host | localhost | -| MG_CERTS_DB_PORT | Database port | 5432 | -| MG_CERTS_DB_PASS | Database password | magistrala | -| MG_CERTS_DB_USER | Database user | magistrala | -| MG_CERTS_DB_NAME | Database name | certs | -| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable | -| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" | -| MG_CERTS_DB_SSL_KEY | Database SSL key | "" | -| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" | -| MG_THINGS_URL | Things service URL | | -| MG_JAEGER_URL | Jaeger server URL | | -| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| MG_CERTS_INSTANCE_ID | Service instance ID | "" | + +| Variable | Description | Default | +| :---------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info | +| MG_CERTS_HTTP_HOST | Service Certs host | "" | +| MG_CERTS_HTTP_PORT | Service Certs port | 9019 | +| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | +| MG_AUTH_GRPC_URL | Auth service gRPC URL | [localhost:8181](localhost:8181) | +| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s | +| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" | +| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" | +| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" | +| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt | +| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key | +| MG_CERTS_VAULT_HOST | Vault host | http://vault:8200 | +| MG_CERTS_VAULT_NAMESPACE | Vault namespace in which pki is present | magistrala | +| MG_CERTS_VAULT_APPROLE_ROLEID | Vault AppRole auth RoleID | magistrala | +| MG_CERTS_VAULT_APPROLE_SECRET | Vault AppRole auth Secret | magistrala | +| MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH | Vault PKI path for issuing Things Certificates | pki_int | +| MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME | Vault PKI Role Name for issuing Things Certificates | magistrala_things_certs | +| MG_CERTS_DB_HOST | Database host | localhost | +| MG_CERTS_DB_PORT | Database port | 5432 | +| MG_CERTS_DB_PASS | Database password | magistrala | +| MG_CERTS_DB_USER | Database user | magistrala | +| MG_CERTS_DB_NAME | Database name | certs | +| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable | +| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" | +| MG_CERTS_DB_SSL_KEY | Database SSL key | "" | +| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" | +| MG_THINGS_URL | Things service URL | [localhost:9000](localhost:9000) | +| MG_JAEGER_URL | Jaeger server URL | [http://localhost:14268/api/traces](http://localhost:14268/api/traces) | +| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| MG_CERTS_INSTANCE_ID | Service instance ID | "" | ## Deployment @@ -95,10 +98,12 @@ MG_AUTH_GRPC_CLIENT_KEY="" \ MG_AUTH_GRPC_SERVER_CERTS="" \ MG_CERTS_SIGN_CA_PATH=ca.crt \ MG_CERTS_SIGN_CA_KEY_PATH=ca.key \ -MG_CERTS_VAULT_HOST="" \ -MG_VAULT_PKI_INT_PATH=pki_int \ -MG_VAULT_CA_ROLE_NAME=magistrala \ -MG_VAULT_TOKEN="" \ +MG_CERTS_VAULT_HOST=http://vault:8200 \ +MG_CERTS_VAULT_NAMESPACE=magistrala \ +MG_CERTS_VAULT_APPROLE_ROLEID=magistrala \ +MG_CERTS_VAULT_APPROLE_SECRET=magistrala \ +MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=pki_int \ +MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=magistrala_things_certs \ MG_CERTS_DB_HOST=localhost \ MG_CERTS_DB_PORT=5432 \ MG_CERTS_DB_PASS=magistrala \ diff --git a/certs/mocks/pki.go b/certs/mocks/pki.go index 3e183b4aad..502917ff99 100644 --- a/certs/mocks/pki.go +++ b/certs/mocks/pki.go @@ -6,6 +6,7 @@ package mocks import ( "bufio" "bytes" + "context" "crypto/ecdsa" "crypto/rand" "crypto/rsa" @@ -160,6 +161,10 @@ func (a *agent) Revoke(serial string) (time.Time, error) { return time.Now(), nil } +func (a *agent) LoginAndRenew(ctx context.Context) error { + return nil +} + func publicKey(priv interface{}) (interface{}, error) { if priv == nil { return nil, errPrivateKeyEmpty diff --git a/certs/pki/vault.go b/certs/pki/vault.go index e1918704a6..91f4617c0a 100644 --- a/certs/pki/vault.go +++ b/certs/pki/vault.go @@ -5,11 +5,14 @@ package pki import ( + "context" "encoding/json" + "log/slog" "time" "github.com/absmach/magistrala/pkg/errors" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/api/auth/approle" "github.com/mitchellh/mapstructure" ) @@ -30,6 +33,13 @@ var ( ErrFailedCertRevocation = errors.New("failed to revoke certificate") errFailedCertDecoding = errors.New("failed to decode response from vault service") + errFailedToLogin = errors.New("failed to login to Vault") + errFailedAppRole = errors.New("failed to create vault new app role") + errNoAuthInfo = errors.New("no auth information from Vault") + errNonRenewal = errors.New("token is not configured to be renewable") + errRenewWatcher = errors.New("unable to initialize new lifetime watcher for renewing auth token") + errFailedRenew = errors.New("failed to renew token") + errCouldNotRenew = errors.New("token can no longer be renewed") ) type Cert struct { @@ -52,10 +62,15 @@ type Agent interface { // Revoke revokes certificate from PKI Revoke(serial string) (time.Time, error) + + // Login to PKI and renews token + LoginAndRenew(ctx context.Context) error } type pkiAgent struct { - token string + appRole string + appSecret string + namespace string path string role string host string @@ -63,6 +78,8 @@ type pkiAgent struct { readURL string revokeURL string client *api.Client + secret *api.Secret + logger *slog.Logger } type certReq struct { @@ -75,7 +92,7 @@ type certRevokeReq struct { } // NewVaultClient instantiates a Vault client. -func NewVaultClient(token, host, path, role string) (Agent, error) { +func NewVaultClient(appRole, appSecret, host, namespace, path, role string, logger *slog.Logger) (Agent, error) { conf := api.DefaultConfig() conf.Address = host @@ -83,13 +100,19 @@ func NewVaultClient(token, host, path, role string) (Agent, error) { if err != nil { return nil, err } - client.SetToken(token) + if namespace != "" { + client.SetNamespace(namespace) + } + p := pkiAgent{ - token: token, + appRole: appRole, + appSecret: appSecret, host: host, + namespace: namespace, role: role, path: path, client: client, + logger: logger, issueURL: "/" + path + "/" + issue + "/" + role, readURL: "/" + path + "/" + cert + "/", revokeURL: "/" + path + "/" + revoke, @@ -162,3 +185,81 @@ func (p *pkiAgent) Revoke(serial string) (time.Time, error) { return time.Unix(0, int64(rev)*int64(time.Second)), nil } + +func (p *pkiAgent) LoginAndRenew(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + p.logger.Info("pki login and renew function stopping") + return nil + default: + err := p.login(ctx) + if err != nil { + p.logger.Info("unable to authenticate to Vault", slog.Any("error", err)) + time.Sleep(5 * time.Second) + break + } + tokenErr := p.manageTokenLifecycle() + if tokenErr != nil { + p.logger.Info("unable to start managing token lifecycle", slog.Any("error", tokenErr)) + time.Sleep(5 * time.Second) + } + } + } +} + +func (p *pkiAgent) login(ctx context.Context) error { + secretID := &approle.SecretID{FromString: p.appSecret} + + authMethod, err := approle.NewAppRoleAuth( + p.appRole, + secretID, + ) + if err != nil { + return errors.Wrap(errFailedAppRole, err) + } + if p.namespace != "" { + p.client.SetNamespace(p.namespace) + } + secret, err := p.client.Auth().Login(ctx, authMethod) + if err != nil { + return errors.Wrap(errFailedToLogin, err) + } + if secret == nil { + return errNoAuthInfo + } + p.secret = secret + return nil +} + +func (p *pkiAgent) manageTokenLifecycle() error { + renew := p.secret.Auth.Renewable + if !renew { + return errNonRenewal + } + + watcher, err := p.client.NewLifetimeWatcher(&api.LifetimeWatcherInput{ + Secret: p.secret, + Increment: 3600, // Requesting token for 3600s = 1h, If this is more than token_max_ttl, then response token will have token_max_ttl + }) + if err != nil { + return errors.Wrap(errRenewWatcher, err) + } + + go watcher.Start() + defer watcher.Stop() + + for { + select { + case err := <-watcher.DoneCh(): + if err != nil { + return errors.Wrap(errFailedRenew, err) + } + // This occurs once the token has reached max TTL or if token is disabled for renewal. + return errCouldNotRenew + + case renewal := <-watcher.RenewCh(): + p.logger.Info("Successfully renewed token", slog.Any("renewed_at", renewal.RenewedAt)) + } + } +} diff --git a/cmd/certs/main.go b/cmd/certs/main.go index 598d91a585..0d0237acfe 100644 --- a/cmd/certs/main.go +++ b/cmd/certs/main.go @@ -57,10 +57,12 @@ type config struct { SignCAKeyPath string `env:"MG_CERTS_SIGN_CA_KEY_PATH" envDefault:"ca.key"` // 3rd party PKI API access settings - PkiHost string `env:"MG_CERTS_VAULT_HOST" envDefault:""` - PkiPath string `env:"MG_VAULT_PKI_INT_PATH" envDefault:"pki_int"` - PkiRole string `env:"MG_VAULT_CA_ROLE_NAME" envDefault:"magistrala"` - PkiToken string `env:"MG_VAULT_TOKEN" envDefault:""` + PkiHost string `env:"MG_CERTS_VAULT_HOST" envDefault:""` + PkiAppRoleID string `env:"MG_CERTS_VAULT_APPROLE_ROLEID" envDefault:""` + PkiAppSecret string `env:"MG_CERTS_VAULT_APPROLE_SECRET" envDefault:""` + PkiNamespace string `env:"MG_CERTS_VAULT_NAMESPACE" envDefault:""` + PkiPath string `env:"MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH" envDefault:"pki_int"` + PkiRole string `env:"MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME" envDefault:"magistrala"` } func main() { @@ -94,13 +96,17 @@ func main() { return } - pkiclient, err := vault.NewVaultClient(cfg.PkiToken, cfg.PkiHost, cfg.PkiPath, cfg.PkiRole) + pkiclient, err := vault.NewVaultClient(cfg.PkiAppRoleID, cfg.PkiAppSecret, cfg.PkiHost, cfg.PkiNamespace, cfg.PkiPath, cfg.PkiRole, logger) if err != nil { logger.Error("failed to configure client for PKI engine") exitCode = 1 return } + g.Go(func() error { + return pkiclient.LoginAndRenew(ctx) + }) + dbConfig := pgclient.Config{Name: defDB} if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil { logger.Error(err.Error()) diff --git a/docker/.env b/docker/.env index c433e7f103..4a5e0bcb84 100644 --- a/docker/.env +++ b/docker/.env @@ -311,13 +311,58 @@ MG_PROVISION_CERTS_HOURS_VALID=2400h MG_PROVISION_CERTS_RSA_BITS=2048 MG_PROVISION_INSTANCE_ID= +### Vault +MG_VAULT_HOST=vault +MG_VAULT_PORT=8200 +MG_VAULT_ADDR=http://vault:8200 +MG_VAULT_NAMESPACE=magistrala +MG_VAULT_UNSEAL_KEY_1= +MG_VAULT_UNSEAL_KEY_2= +MG_VAULT_UNSEAL_KEY_3= +MG_VAULT_TOKEN= + +MG_VAULT_PKI_PATH=pki +MG_VAULT_PKI_ROLE_NAME=magistrala_int_ca +MG_VAULT_PKI_FILE_NAME=mg_root +MG_VAULT_PKI_CA_CN='Magistrala Root Certificate Authority' +MG_VAULT_PKI_CA_OU='Magistrala' +MG_VAULT_PKI_CA_O='Magistrala' +MG_VAULT_PKI_CA_C='FRANCE' +MG_VAULT_PKI_CA_L='PARIS' +MG_VAULT_PKI_CA_ST='PARIS' +MG_VAULT_PKI_CA_ADDR='5 Av. Anatole' +MG_VAULT_PKI_CA_PO='75007' +MG_VAULT_PKI_CLUSTER_PATH=http://localhost +MG_VAULT_PKI_CLUSTER_AIA_PATH=http://localhost + +MG_VAULT_PKI_INT_PATH=pki_int +MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME=magistrala_server_certs +MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME=magistrala_things_certs +MG_VAULT_PKI_INT_FILE_NAME=mg_int +MG_VAULT_PKI_INT_CA_CN='Magistrala Intermediate Certificate Authority' +MG_VAULT_PKI_INT_CA_OU='Magistrala' +MG_VAULT_PKI_INT_CA_O='Magistrala' +MG_VAULT_PKI_INT_CA_C='FRANCE' +MG_VAULT_PKI_INT_CA_L='PARIS' +MG_VAULT_PKI_INT_CA_ST='PARIS' +MG_VAULT_PKI_INT_CA_ADDR='5 Av. Anatole' +MG_VAULT_PKI_INT_CA_PO='75007' +MG_VAULT_PKI_INT_CLUSTER_PATH=http://localhost +MG_VAULT_PKI_INT_CLUSTER_AIA_PATH=http://localhost + +MG_VAULT_THINGS_CERTS_ISSUER_ROLEID=magistrala +MG_VAULT_THINGS_CERTS_ISSUER_SECRET=magistrala + # Certs MG_CERTS_LOG_LEVEL=debug MG_CERTS_SIGN_CA_PATH=/etc/ssl/certs/ca.crt MG_CERTS_SIGN_CA_KEY_PATH=/etc/ssl/certs/ca.key -MG_CERTS_VAULT_HOST=http://vault:8200 -MG_VAULT_PKI_INT_PATH=pki_int -MG_VAULT_CA_ROLE_NAME=magistrala +MG_CERTS_VAULT_HOST=${MG_VAULT_ADDR} +MG_CERTS_VAULT_NAMESPACE=${MG_VAULT_NAMESPACE} +MG_CERTS_VAULT_APPROLE_ROLEID=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} +MG_CERTS_VAULT_APPROLE_SECRET=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} +MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=${MG_VAULT_PKI_INT_PATH} +MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} MG_CERTS_HTTP_HOST=certs MG_CERTS_HTTP_PORT=9019 MG_CERTS_HTTP_SERVER_CERT= @@ -333,22 +378,6 @@ MG_CERTS_DB_SSL_KEY= MG_CERTS_DB_SSL_ROOT_CERT= MG_CERTS_INSTANCE_ID= -### Vault -MG_VAULT_HOST=vault -MG_VAULT_PORT=8200 -MG_VAULT_UNSEAL_KEY_1= -MG_VAULT_UNSEAL_KEY_2= -MG_VAULT_UNSEAL_KEY_3= -MG_VAULT_TOKEN= -MG_VAULT_CA_NAME=magistrala -MG_VAULT_CA_ROLE_NAME=magistrala -MG_VAULT_PKI_PATH=pki -MG_VAULT_PKI_INT_PATH=pki_int -MG_VAULT_CA_CN=magistrala.com -MG_VAULT_CA_OU='Magistrala Cloud' -MG_VAULT_CA_O='Magistrala Labs' -MG_VAULT_CA_C=Serbia -MG_VAULT_CA_L=Belgrade ### LoRa MG_LORA_ADAPTER_LOG_LEVEL=debug diff --git a/docker/README.md b/docker/README.md index 9361bcc0c8..4f36f96e77 100644 --- a/docker/README.md +++ b/docker/README.md @@ -117,3 +117,18 @@ services: volumes: - magistrala-broker-volume:/data ``` + +## Nginx Configuration + +Nginx is the entry point for all traffic to Magistrala. +By using environment variables file at `docker/.env` you can modify the below given Nginx directive. + +`MG_NGINX_SERVER_NAME` environmental variable is used to configure nginx directive `server_name`. If environmental variable `MG_NGINX_SERVER_NAME` is empty then default value `localhost` will set to `server_name`. + +`MG_NGINX_SERVER_CERT` environmental variable is used to configure nginx directive `ssl_certificate`. If environmental variable `MG_NGINX_SERVER_CERT` is empty then by default server certificate in the path `docker/ssl/certs/magistrala-server.crt` will be assigned. + +`MG_NGINX_SERVER_KEY` environmental variable is used to configure nginx directive `ssl_certificate_key`. If environmental variable `MG_NGINX_SERVER_KEY` is empty then by default server certificate key in the path `docker/ssl/certs/magistrala-server.key` will be assigned. + +`MG_NGINX_SERVER_CLIENT_CA` environmental variable is used to configure nginx directive `ssl_client_certificate`. If environmental variable `MG_NGINX_SERVER_CLIENT_CA` is empty then by default certificate in the path `docker/ssl/certs/ca.crt` will be assigned. + +`MG_NGINX_SERVER_DHPARAM` environmental variable is used to configure nginx directive `ssl_dhparam`. If environmental variable `MG_NGINX_SERVER_DHPARAM` is empty then by default file in the path `docker/ssl/dhparam.pem` will be assigned. diff --git a/docker/addons/certs/docker-compose.yml b/docker/addons/certs/docker-compose.yml index f5b1591a8e..41b9661161 100644 --- a/docker/addons/certs/docker-compose.yml +++ b/docker/addons/certs/docker-compose.yml @@ -44,10 +44,11 @@ services: MG_CERTS_SIGN_CA_PATH: ${MG_CERTS_SIGN_CA_PATH} MG_CERTS_SIGN_CA_KEY_PATH: ${MG_CERTS_SIGN_CA_KEY_PATH} MG_CERTS_VAULT_HOST: ${MG_CERTS_VAULT_HOST} - MG_VAULT_PKI_INT_PATH: ${MG_VAULT_PKI_INT_PATH} - MG_VAULT_CA_ROLE_NAME: ${MG_VAULT_CA_ROLE_NAME} - MG_VAULT_PKI_PATH: ${MG_VAULT_PKI_PATH} - MG_VAULT_TOKEN: ${MG_VAULT_TOKEN} + MG_CERTS_VAULT_NAMESPACE: ${MG_CERTS_VAULT_NAMESPACE} + MG_CERTS_VAULT_APPROLE_ROLEID: ${MG_CERTS_VAULT_APPROLE_ROLEID} + MG_CERTS_VAULT_APPROLE_SECRET: ${MG_CERTS_VAULT_APPROLE_SECRET} + MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH} + MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME: ${MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME} MG_CERTS_HTTP_HOST: ${MG_CERTS_HTTP_HOST} MG_CERTS_HTTP_PORT: ${MG_CERTS_HTTP_PORT} MG_CERTS_HTTP_SERVER_CERT: ${MG_CERTS_HTTP_SERVER_CERT} diff --git a/docker/addons/vault/.gitignore b/docker/addons/vault/.gitignore index 167a4fd8d7..4f14d396c2 100644 --- a/docker/addons/vault/.gitignore +++ b/docker/addons/vault/.gitignore @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 data +magistrala_things_certs_issue.hcl diff --git a/docker/addons/vault/README.md b/docker/addons/vault/README.md index d21b3c0221..193fa4dafa 100644 --- a/docker/addons/vault/README.md +++ b/docker/addons/vault/README.md @@ -6,35 +6,53 @@ When the Vault service is started, some initialization steps need to be done to ## Configuration -| Variable | Description | Default | -| --------------------- | ------------------------------------------------------- | ---------------- | -| MG_VAULT_HOST | Vault service address | vault | -| MG_VAULT_PORT | Vault service port | 8200 | -| MG_VAULT_UNSEAL_KEY_1 | Vault unseal key | "" | -| MG_VAULT_UNSEAL_KEY_2 | Vault unseal key | "" | -| MG_VAULT_UNSEAL_KEY_3 | Vault unseal key | "" | -| MG_VAULT_TOKEN | Vault cli access token | "" | -| MG_VAULT_PKI_PATH | Vault secrets engine path for CA | pki | -| MG_VAULT_PKI_INT_PATH | Vault secrets engine path for intermediate CA | pki_int | -| MG_VAULT_CA_ROLE_NAME | Vault secrets engine role | magistrala | -| MG_VAULT_CA_NAME | Certificates name used by `vault-set-pki.sh` | magistrala | -| MG_VAULT_CA_CN | Common name used for CA creation by `vault-set-pki.sh` | magistrala.com | -| MG_VAULT_CA_OU | Org unit used for CA creation by `vault-set-pki.sh` | Magistrala Cloud | -| MG_VAULT_CA_O | Organization used for CA creation by `vault-set-pki.sh` | Magistrala Labs | -| MG_VAULT_CA_C | Country used for CA creation by `vault-set-pki.sh` | Serbia | -| MG_VAULT_CA_L | Location used for CA creation by `vault-set-pki.sh` | Belgrade | + +| Variable | Description | Default | +| :---------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------- | +| MG_VAULT_HOST | Vault service address | vault | +| MG_VAULT_PORT | Vault service port | 8200 | +| MG_VAULT_ADDR | Vault Address | http://vault:8200 | +| MG_VAULT_UNSEAL_KEY_1 | Vault unseal key | "" | +| MG_VAULT_UNSEAL_KEY_2 | Vault unseal key | "" | +| MG_VAULT_UNSEAL_KEY_3 | Vault unseal key | "" | +| MG_VAULT_TOKEN | Vault cli access token | "" | +| MG_VAULT_PKI_PATH | Vault secrets engine path for Root CA | pki | +| MG_VAULT_PKI_ROLE_NAME | Vault Root CA role name to issue intermediate CA | magistrala_int_ca | +| MG_VAULT_PKI_FILE_NAME | Root CA Certificates name used by`vault_set_pki.sh` | mg_root | +| MG_VAULT_PKI_CA_CN | Common name used for Root CA creation by`vault_set_pki.sh` | Magistrala Root Certificate Authority | +| MG_VAULT_PKI_CA_OU | Organization unit used for Root CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_CA_O | Organization used for Root CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_CA_C | Country used for Root CA creation by`vault_set_pki.sh` | FRANCE | +| MG_VAULT_PKI_CA_L | Location used for Root CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_CA_ST | State or Provisions used for Root CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_CA_ADDR | Address used for Root CA creation by`vault_set_pki.sh` | 5 Av. Anatole | +| MG_VAULT_PKI_CA_PO | Postal code used for Root CA creation by`vault_set_pki.sh` | 75007 | +| MG_VAULT_PKI_CLUSTER_PATH | Vault Root CA Cluster Path | http://localhost | +| MG_VAULT_PKI_CLUSTER_AIA_PATH | Vault Root CA Cluster AIA Path | http://localhost | +| MG_VAULT_PKI_INT_PATH | Vault secrets engine path for Intermediate CA | pki_int | +| MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue server certificate | magistrala_server_certs | +| MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME | Vault Intermediate CA role name to issue Things certificates | magistrala_things_certs | +| MG_VAULT_PKI_INT_FILE_NAME | Intermediate CA Certificates name used by`vault_set_pki.sh` | mg_root | +| MG_VAULT_PKI_INT_CA_CN | Common name used for Intermediate CA creation by`vault_set_pki.sh` | Magistrala Root Certificate Authority | +| MG_VAULT_PKI_INT_CA_OU | Organization unit used for Root CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_INT_CA_O | Organization used for Intermediate CA creation by`vault_set_pki.sh` | Magistrala | +| MG_VAULT_PKI_INT_CA_C | Country used for Intermediate CA creation by`vault_set_pki.sh` | FRANCE | +| MG_VAULT_PKI_INT_CA_L | Location used for Intermediate CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_INT_CA_ST | State or Provisions used for Intermediate CA creation by`vault_set_pki.sh` | PARIS | +| MG_VAULT_PKI_INT_CA_ADDR | Address used for Intermediate CA creation by`vault_set_pki.sh` | 5 Av. Anatole | +| MG_VAULT_PKI_INT_CA_PO | Postal code used for Intermediate CA creation by`vault_set_pki.sh` | 75007 | +| MG_VAULT_PKI_INT_CLUSTER_PATH | Vault Intermediate CA Cluster Path | http://localhost | +| MG_VAULT_PKI_INT_CLUSTER_AIA_PATH | Vault Intermediate CA Cluster AIA Path | http://localhost | +| MG_VAULT_THINGS_CERTS_ISSUER_ROLEID | Vault Intermediate CA Things Certificate issuer AppRole authentication RoleID | magistrala | +| MG_VAULT_THINGS_CERTS_ISSUER_SECRET | Vault Intermediate CA Things Certificate issuer AppRole authentication Secret | magistrala | ## Setup The following scripts are provided, which work on the running Vault service in Docker. -1. `vault-init.sh` - -Calls `vault operator init` to perform the initial vault initialization and generates -a `data/secrets` file which contains the Vault unseal keys and root tokens. +### 1. `vault_init.sh` -After this step, the corresponding Vault environment variables (`MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, -`MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`) should be updated in `.env` file. +Calls `vault operator init` to perform the initial vault initialization and generates a `docker/addons/vault/data/secrets` file which contains the Vault unseal keys and root tokens. Example contents for `data/secrets`: @@ -62,23 +80,43 @@ bash-4.4 Use 3 out of five keys presented and put it into .env file and than start the composition again Vault should be in unsealed state ( take a note that this is not recommended in terms of security, this is deployment for development) A real production deployment can use Vault auto unseal mode where vault gets unseal keys from some 3rd party KMS ( on AWS for example) ``` -2. `vault-unseal.sh` +### 2. `vault_copy_env.sh` + +After first step, the corresponding Vault environment variables (`MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`) should be updated in `.env` file. + +`vault_copy_env.sh` scripts copies values from `docker/addons/vault/data/secrets` file and update environmental variables `MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3` present in `.env` file. + +### 3. `vault_unseal.sh` This can be run after the initialization to unseal Vault, which is necessary for it to be used to store and/or get secrets. + This can be used if you don't want to restart the service. -The unseal environment variables need to be set in `.env` for the script to work (`MG_VAULT_TOKEN`, `MG_VAULT_UNSEAL_KEY_1`, -`MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`). +The unseal environment variables need to be set in `.env` for the script to work (`MG_VAULT_TOKEN`,`MG_VAULT_UNSEAL_KEY_1`, `MG_VAULT_UNSEAL_KEY_2`, `MG_VAULT_UNSEAL_KEY_3`). + +This script should not be necessary to run after the initial setup, since the Vault service unseals itself when starting the container. + +### 4. `vault_set_pki.sh` + +This script is used to generate the root certificate, intermediate certificate and HTTPS server certificate. +All generate certificates, keys and CSR by `vault_set_pki.sh` will be present at `docker/addons/vault/data`. + +The parameters required for generating certificate are obtained from the environment variables which are loaded from `docker/.env`. + +Environmental variables starting with `MG_VAULT_PKI` in `docker/.env` file are used by `vault_set_pki.sh` to generate root CA. +Environmental variables starting with`MG_VAULT_PKI_INT` in `docker/.env` file are used by `vault_set_pki.sh` to generate intermediate CA. + +### 5. `vault_create_approle.sh` -This script should not be necessary to run after the initial setup, since the Vault service unseals itself when -starting the container. +This script is used to enable app role authorization in Vault. Certs service used the approle credentials to issue, revoke things certificate from vault intermedate CA. -3. `vault-set-pki.sh` +`vault_create_approle.sh` script by default tries to enable auth approle. +If approle is already enabled in vault, then use args `skip_enable_app_role` to skip enable auth approle step. +To skip enable auth approle step use the following `vault_create_approle.sh skip_enable_app_role` -This script is used to generate the root certificate, intermediate certificate and HTTPS server certificate. -After it runs, it copies the necessary certificates and keys to the `docker/ssl/certs` folder. +### 6. `vault_copy_certs.sh` -The CA parameters are obtained from the environment variables starting with `MG_VAULT_CA` in `.env` file. +This scripts copies the necessary certificates and keys from `docker/addons/vault/data` to the `docker/ssl/certs` folder. ## Vault CLI diff --git a/docker/addons/vault/magistrala_things_certs_issue.template.hcl b/docker/addons/vault/magistrala_things_certs_issue.template.hcl new file mode 100644 index 0000000000..1b13f6db1b --- /dev/null +++ b/docker/addons/vault/magistrala_things_certs_issue.template.hcl @@ -0,0 +1,32 @@ + +# Allow issue certificate with role with default issuer from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME}" { + capabilities = ["create", "update"] +} + +## Revole certificate from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/revoke" { + capabilities = ["create", "update"] +} + +## List Revoked Certificates from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/certs/revoked" { + capabilities = ["list"] +} + + +## List Certificates from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/certs" { + capabilities = ["list"] +} + +## Read Certificate from Intermediate PKI +path "${MG_VAULT_PKI_INT_PATH}/cert/+" { + capabilities = ["read"] +} +path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw" { + capabilities = ["read"] +} +path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw/pem" { + capabilities = ["read"] +} diff --git a/docker/addons/vault/vault-set-pki.sh b/docker/addons/vault/vault-set-pki.sh deleted file mode 100755 index c2a7003d4e..0000000000 --- a/docker/addons/vault/vault-set-pki.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -export MAGISTRALA_DIR=$scriptdir/../../../ - -cd $scriptdir - -readDotEnv() { - set -o allexport - source $MAGISTRALA_DIR/docker/.env - set +o allexport -} - -vault() { - docker exec -it magistrala-vault vault "$@" -} - -vaultEnablePKI() { - vault secrets enable -path ${MG_VAULT_PKI_PATH} pki - vault secrets tune -max-lease-ttl=87600h ${MG_VAULT_PKI_PATH} -} - -vaultAddRoleToSecret() { - vault write ${MG_VAULT_PKI_PATH}/roles/${MG_VAULT_CA_NAME} \ - allow_any_name=true \ - max_ttl="4300h" \ - default_ttl="4300h" \ - generate_lease=true -} - -vaultGenerateRootCACertificate() { - echo "Generate root CA certificate" - vault write -format=json ${MG_VAULT_PKI_PATH}/root/generate/exported \ - common_name="\"$MG_VAULT_CA_CN CA Root\"" \ - ou="\"$MG_VAULT_CA_OU\""\ - organization="\"$MG_VAULT_CA_O\"" \ - country="\"$MG_VAULT_CA_C\"" \ - locality="\"$MG_VAULT_CA_L\"" \ - ttl=87600h | tee >(jq -r .data.certificate >data/${MG_VAULT_CA_NAME}_ca.crt) \ - >(jq -r .data.issuing_ca >data/${MG_VAULT_CA_NAME}_issuing_ca.crt) \ - >(jq -r .data.private_key >data/${MG_VAULT_CA_NAME}_ca.key) -} - -vaultGenerateIntermediateCAPKI() { - echo "Generate Intermediate CA PKI" - vault secrets enable -path=${MG_VAULT_PKI_INT_PATH} pki - vault secrets tune -max-lease-ttl=43800h ${MG_VAULT_PKI_INT_PATH} -} - -vaultGenerateIntermediateCSR() { - echo "Generate intermediate CSR" - vault write -format=json ${MG_VAULT_PKI_INT_PATH}/intermediate/generate/exported \ - common_name="$MG_VAULT_CA_CN Intermediate Authority" \ - | tee >(jq -r .data.csr >data/${MG_VAULT_CA_NAME}_int.csr) \ - >(jq -r .data.private_key >data/${MG_VAULT_CA_NAME}_int.key) -} - -vaultSignIntermediateCSR() { - echo "Sign intermediate CSR" - docker cp data/${MG_VAULT_CA_NAME}_int.csr magistrala-vault:/vault/${MG_VAULT_CA_NAME}_int.csr - vault write -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ - csr=@/vault/${MG_VAULT_CA_NAME}_int.csr \ - | tee >(jq -r .data.certificate >data/${MG_VAULT_CA_NAME}_int.crt) \ - >(jq -r .data.issuing_ca >data/${MG_VAULT_CA_NAME}_int_issuing_ca.crt) -} - -vaultInjectIntermediateCertificate() { - echo "Inject Intermediate Certificate" - docker cp data/${MG_VAULT_CA_NAME}_int.crt magistrala-vault:/vault/${MG_VAULT_CA_NAME}_int.crt - vault write ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@/vault/${MG_VAULT_CA_NAME}_int.crt -} - -vaultGenerateIntermediateCertificateBundle() { - echo "Generate intermediate certificate bundle" - cat data/${MG_VAULT_CA_NAME}_int.crt data/${MG_VAULT_CA_NAME}_ca.crt \ - > data/${MG_VAULT_CA_NAME}_int_bundle.crt -} - -vaultSetupIssuingURLs() { - echo "Setup URLs for CRL and issuing" - VAULT_ADDR=http://$MG_VAULT_HOST:$MG_VAULT_PORT - vault write ${MG_VAULT_PKI_INT_PATH}/config/urls \ - issuing_certificates="$VAULT_ADDR/v1/${MG_VAULT_PKI_INT_PATH}/ca" \ - crl_distribution_points="$VAULT_ADDR/v1/${MG_VAULT_PKI_INT_PATH}/crl" -} - -vaultSetupCARole() { - echo "Setup CA role" - vault write ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_CA_ROLE_NAME} \ - allow_subdomains=true \ - allow_any_name=true \ - max_ttl="720h" -} - -vaultGenerateServerCertificate() { - echo "Generate server certificate" - vault write -format=json ${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_CA_ROLE_NAME} \ - common_name="$MG_VAULT_CA_CN" ttl="8670h" \ - | tee >(jq -r .data.certificate >data/${MG_VAULT_CA_CN}.crt) \ - >(jq -r .data.private_key >data/${MG_VAULT_CA_CN}.key) -} - -vaultCleanupFiles() { - docker exec magistrala-vault sh -c 'rm -rf /vault/*.{crt,csr}' -} - -if ! command -v jq &> /dev/null -then - echo "jq command could not be found, please install it and try again." - exit -fi - -readDotEnv - -mkdir -p data - -vault login ${MG_VAULT_TOKEN} - -vaultEnablePKI -vaultAddRoleToSecret -vaultGenerateRootCACertificate -vaultGenerateIntermediateCAPKI -vaultGenerateIntermediateCSR -vaultSignIntermediateCSR -vaultInjectIntermediateCertificate -vaultGenerateIntermediateCertificateBundle -vaultSetupIssuingURLs -vaultSetupCARole -vaultGenerateServerCertificate -vaultCleanupFiles - -echo "Copying certificate files" - -cp -v data/${MG_VAULT_CA_CN}.crt ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.crt -cp -v data/${MG_VAULT_CA_CN}.key ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.key -cp -v data/${MG_VAULT_CA_NAME}_int.key ${MAGISTRALA_DIR}/docker/ssl/certs/ca.key -cp -v data/${MG_VAULT_CA_NAME}_int.crt ${MAGISTRALA_DIR}/docker/ssl/certs/ca.crt -cp -v data/${MG_VAULT_CA_NAME}_int_bundle.crt ${MAGISTRALA_DIR}/docker/ssl/bundle.pem - -exit 0 diff --git a/docker/addons/vault/vault_copy_certs.sh b/docker/addons/vault/vault_copy_certs.sh new file mode 100755 index 0000000000..1f00a8f516 --- /dev/null +++ b/docker/addons/vault/vault_copy_certs.sh @@ -0,0 +1,33 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +readDotEnv() { + set -o allexport + source $MAGISTRALA_DIR/docker/.env + set +o allexport +} + +readDotEnv + +server_name="localhost" + +# Check if MG_NGINX_SERVER_NAME is set or not empty +if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then + server_name="$MG_NGINX_SERVER_NAME" +fi + +echo "Copying certificate files" +cp -v data/${server_name}.crt ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.crt +cp -v data/${server_name}.key ${MAGISTRALA_DIR}/docker/ssl/certs/magistrala-server.key +cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}.key ${MAGISTRALA_DIR}/docker/ssl/certs/ca.key +cp -v data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt ${MAGISTRALA_DIR}/docker/ssl/certs/ca.crt + +exit 0 diff --git a/docker/addons/vault/vault-init.sh b/docker/addons/vault/vault_copy_env.sh similarity index 84% rename from docker/addons/vault/vault-init.sh rename to docker/addons/vault/vault_copy_env.sh index d8ab5cbbbe..92c773e513 100755 --- a/docker/addons/vault/vault-init.sh +++ b/docker/addons/vault/vault_copy_env.sh @@ -7,18 +7,13 @@ set -euo pipefail scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" export MAGISTRALA_DIR=$scriptdir/../../../ +cd $scriptdir + write_env() { sed -i "s,MG_VAULT_UNSEAL_KEY_1=.*,MG_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env sed -i "s,MG_VAULT_UNSEAL_KEY_2=.*,MG_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env sed -i "s,MG_VAULT_UNSEAL_KEY_3=.*,MG_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env sed -i "s,MG_VAULT_TOKEN=.*,MG_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' data/secrets)," $MAGISTRALA_DIR/docker/.env } -vault() { - docker exec -it magistrala-vault vault "$@" -} - -mkdir -p data - -vault operator init 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > data/secrets) write_env diff --git a/docker/addons/vault/vault_create_approle.sh b/docker/addons/vault/vault_create_approle.sh new file mode 100755 index 0000000000..59b8b44c7a --- /dev/null +++ b/docker/addons/vault/vault_create_approle.sh @@ -0,0 +1,95 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +SKIP_ENABLE_APP_ROLE=${1:-} + +readDotEnv() { + set -o allexport + source $MAGISTRALA_DIR/docker/.env + set +o allexport +} + +vault() { + docker exec -it magistrala-vault vault "$@" +} + +vaultCreatePolicyFile() { + envsubst ' + ${MG_VAULT_PKI_INT_PATH} + ${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} + ' < magistrala_things_certs_issue.template.hcl > magistrala_things_certs_issue.hcl +} +vaultCreatePolicy() { + echo "Creating new policy for AppRole" + docker cp magistrala_things_certs_issue.hcl magistrala-vault:/vault/magistrala_things_certs_issue.hcl + vault policy write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} magistrala_things_certs_issue /vault/magistrala_things_certs_issue.hcl +} + +vaultEnableAppRole() { + if [ "$SKIP_ENABLE_APP_ROLE" == "skip_enable_app_role" ]; then + echo "Skipping Enable AppRole" + else + echo "Enabling AppRole" + vault auth enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} approle + fi +} + +vaultDeleteRole() { + echo "Deleteing old AppRole" + vault delete -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer +} + +vaultCreateRole() { + echo "Creating new AppRole" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer \ + token_policies=magistrala_things_certs_issue secret_id_num_uses=0 \ + secret_id_ttl=0 token_ttl=1h token_max_ttl=3h token_num_uses=0 +} + +vaultWriteCustomRoleID(){ + echo "Writing custom role id" + vault read -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/role-id role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} +} + +vaultWriteCustomSecret() { + echo "Writing custom secret" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -f auth/approle/role/magistrala_things_certs_issuer/secret-id + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/role/magistrala_things_certs_issuer/custom-secret-id secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} num_uses=0 ttl=0 +} + +vaultTestRoleLogin() { + echo "Testing custom roleid secret by logging in" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} auth/approle/login \ + role_id=${MG_VAULT_THINGS_CERTS_ISSUER_ROLEID} \ + secret_id=${MG_VAULT_THINGS_CERTS_ISSUER_SECRET} + +} +if ! command -v jq &> /dev/null +then + echo "jq command could not be found, please install it and try again." + exit +fi + +readDotEnv + +vault login -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_TOKEN} + +vaultCreatePolicyFile +vaultCreatePolicy +vaultEnableAppRole +vaultDeleteRole +vaultCreateRole +vaultWriteCustomRoleID +vaultWriteCustomSecret +vaultTestRoleLogin + +exit 0 diff --git a/docker/addons/vault/vault_init.sh b/docker/addons/vault/vault_init.sh new file mode 100755 index 0000000000..e375cbc23a --- /dev/null +++ b/docker/addons/vault/vault_init.sh @@ -0,0 +1,18 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +vault() { + docker exec -it magistrala-vault vault "$@" +} + +mkdir -p data + +vault operator init 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > data/secrets) diff --git a/docker/addons/vault/vault_set_pki.sh b/docker/addons/vault/vault_set_pki.sh new file mode 100755 index 0000000000..51bfee1cff --- /dev/null +++ b/docker/addons/vault/vault_set_pki.sh @@ -0,0 +1,199 @@ +#!/usr/bin/bash +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +scriptdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export MAGISTRALA_DIR=$scriptdir/../../../ + +cd $scriptdir + +readDotEnv() { + set -o allexport + source $MAGISTRALA_DIR/docker/.env + set +o allexport +} + +server_name="localhost" + +# Check if MG_NGINX_SERVER_NAME is set or not empty +if [ -n "${MG_NGINX_SERVER_NAME:-}" ]; then + server_name="$MG_NGINX_SERVER_NAME" +fi + +vault() { + docker exec -it magistrala-vault vault "$@" +} + +vaultEnablePKI() { + vault secrets enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -path ${MG_VAULT_PKI_PATH} pki + vault secrets tune -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -max-lease-ttl=87600h ${MG_VAULT_PKI_PATH} +} + +vaultConfigPKIClusterPath() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/cluster aia_path=${MG_VAULT_PKI_CLUSTER_AIA_PATH} path=${MG_VAULT_PKI_CLUSTER_PATH} +} + +vaultConfigPKICrl() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/crl expiry="5m" ocsp_disable=false ocsp_expiry=0 auto_rebuild=true auto_rebuild_grace_period="2m" enable_delta=true delta_rebuild_interval="1m" +} + +vaultAddRoleToSecret() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/roles/${MG_VAULT_PKI_ROLE_NAME} \ + allow_any_name=true \ + max_ttl="8760h" \ + default_ttl="8760h" \ + generate_lease=true +} + +vaultGenerateRootCACertificate() { + echo "Generate root CA certificate" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/generate/exported \ + common_name="\"$MG_VAULT_PKI_CA_CN\"" \ + ou="\"$MG_VAULT_PKI_CA_OU\"" \ + organization="\"$MG_VAULT_PKI_CA_O\"" \ + country="\"$MG_VAULT_PKI_CA_C\"" \ + locality="\"$MG_VAULT_PKI_CA_L\"" \ + province="\"$MG_VAULT_PKI_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_CA_PO\"" \ + ttl=87600h | tee >(jq -r .data.certificate >data/${MG_VAULT_PKI_FILE_NAME}_ca.crt) \ + >(jq -r .data.issuing_ca >data/${MG_VAULT_PKI_FILE_NAME}_issuing_ca.crt) \ + >(jq -r .data.private_key >data/${MG_VAULT_PKI_FILE_NAME}_ca.key) +} + +vaultSetupRootCAIssuingURLs() { + echo "Setup URLs for CRL and issuing" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_PATH}/config/urls \ + issuing_certificates="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/ca" \ + crl_distribution_points="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/crl" \ + ocsp_servers="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_PATH}/ocsp" \ + enable_templating=true +} + +vaultGenerateIntermediateCAPKI() { + echo "Generate Intermediate CA PKI" + vault secrets enable -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -path=${MG_VAULT_PKI_INT_PATH} pki + vault secrets tune -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -max-lease-ttl=43800h ${MG_VAULT_PKI_INT_PATH} +} + +vaultConfigIntermediatePKIClusterPath() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/cluster aia_path=${MG_VAULT_PKI_INT_CLUSTER_AIA_PATH} path=${MG_VAULT_PKI_INT_CLUSTER_PATH} +} + +vaultConfigIntermediatePKICrl() { + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/crl expiry="5m" ocsp_disable=false ocsp_expiry=0 auto_rebuild=true auto_rebuild_grace_period="2m" enable_delta=true delta_rebuild_interval="1m" +} + +vaultGenerateIntermediateCSR() { + echo "Generate intermediate CSR" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/intermediate/generate/exported \ + common_name="\"$MG_VAULT_PKI_INT_CA_CN\"" \ + ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ + organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ + country="\"$MG_VAULT_PKI_INT_CA_C\"" \ + locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ + province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ + | tee >(jq -r .data.csr >data/${MG_VAULT_PKI_INT_FILE_NAME}.csr) \ + >(jq -r .data.private_key >data/${MG_VAULT_PKI_INT_FILE_NAME}.key) +} + +vaultSignIntermediateCSR() { + echo "Sign intermediate CSR" + docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.csr magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_PATH}/root/sign-intermediate \ + csr=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.csr ttl="8760h" \ + ou="\"$MG_VAULT_PKI_INT_CA_OU\""\ + organization="\"$MG_VAULT_PKI_INT_CA_O\"" \ + country="\"$MG_VAULT_PKI_INT_CA_C\"" \ + locality="\"$MG_VAULT_PKI_INT_CA_L\"" \ + province="\"$MG_VAULT_PKI_INT_CA_ST\"" \ + street_address="\"$MG_VAULT_PKI_INT_CA_ADDR\"" \ + postal_code="\"$MG_VAULT_PKI_INT_CA_PO\"" \ + | tee >(jq -r .data.certificate >data/${MG_VAULT_PKI_INT_FILE_NAME}.crt) \ + >(jq -r .data.issuing_ca >data/${MG_VAULT_PKI_INT_FILE_NAME}_issuing_ca.crt) +} + +vaultInjectIntermediateCertificate() { + echo "Inject Intermediate Certificate" + docker cp data/${MG_VAULT_PKI_INT_FILE_NAME}.crt magistrala-vault:/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/intermediate/set-signed certificate=@/vault/${MG_VAULT_PKI_INT_FILE_NAME}.crt +} + +vaultGenerateIntermediateCertificateBundle() { + echo "Generate intermediate certificate bundle" + cat data/${MG_VAULT_PKI_INT_FILE_NAME}.crt data/${MG_VAULT_PKI_FILE_NAME}_ca.crt \ + > data/${MG_VAULT_PKI_INT_FILE_NAME}_bundle.crt +} + +vaultSetupIntermediateIssuingURLs() { + echo "Setup URLs for CRL and issuing" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/config/urls \ + issuing_certificates="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/ca" \ + crl_distribution_points="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/crl" \ + ocsp_servers="{{cluster_aia_path}}/v1/${MG_VAULT_PKI_INT_PATH}/ocsp" \ + enable_templating=true +} + +vaultSetupServerCertsRole() { + echo "Setup Server Certs role" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ + allow_subdomains=true \ + max_ttl="4320h" +} + +vaultGenerateServerCertificate() { + echo "Generate server certificate" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} -format=json ${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_SERVER_CERTS_ROLE_NAME} \ + common_name="$server_name" ttl="4320h" \ + | tee >(jq -r .data.certificate >data/${server_name}.crt) \ + >(jq -r .data.private_key >data/${server_name}.key) +} + +vaultSetupThingCertsRole() { + echo "Setup Thing Certs role" + vault write -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_PKI_INT_PATH}/roles/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME} \ + allow_subdomains=true \ + allow_any_name=true \ + max_ttl="2160h" +} + +vaultCleanupFiles() { + docker exec magistrala-vault sh -c 'rm -rf /vault/*.{crt,csr}' +} + +if ! command -v jq &> /dev/null +then + echo "jq command could not be found, please install it and try again." + exit +fi + +readDotEnv + +mkdir -p data + +vault login -namespace=${MG_VAULT_NAMESPACE} -address=${MG_VAULT_ADDR} ${MG_VAULT_TOKEN} + +vaultEnablePKI +vaultConfigPKIClusterPath +vaultConfigPKICrl +vaultAddRoleToSecret +vaultGenerateRootCACertificate +vaultSetupRootCAIssuingURLs +vaultGenerateIntermediateCAPKI +vaultConfigIntermediatePKIClusterPath +vaultConfigIntermediatePKICrl +vaultGenerateIntermediateCSR +vaultSignIntermediateCSR +vaultInjectIntermediateCertificate +vaultGenerateIntermediateCertificateBundle +vaultSetupIntermediateIssuingURLs +vaultSetupServerCertsRole +vaultGenerateServerCertificate +vaultSetupThingCertsRole +vaultCleanupFiles + +exit 0 diff --git a/docker/addons/vault/vault-unseal.sh b/docker/addons/vault/vault_unseal.sh similarity index 100% rename from docker/addons/vault/vault-unseal.sh rename to docker/addons/vault/vault_unseal.sh diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2e501cd887..93781c863e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -234,10 +234,18 @@ services: - ./nginx/entrypoint.sh:/docker-entrypoint.d/entrypoint.sh - ./nginx/snippets:/etc/nginx/snippets - ./ssl/authorization.js:/etc/nginx/authorization.js - - ./ssl/certs/magistrala-server.crt:/etc/ssl/certs/magistrala-server.crt - - ./ssl/certs/ca.crt:/etc/ssl/certs/ca.crt - - ./ssl/certs/magistrala-server.key:/etc/ssl/private/magistrala-server.key - - ./ssl/dhparam.pem:/etc/ssl/certs/dhparam.pem + - type: bind + source: ${MG_NGINX_SERVER_CERT:-./ssl/certs/magistrala-server.crt} + target: /etc/ssl/certs/magistrala-server.crt + - type: bind + source: ${MG_NGINX_SERVER_KEY:-./ssl/certs/magistrala-server.key} + target: /etc/ssl/private/magistrala-server.key + - type: bind + source: ${MG_NGINX_SERVER_CLIENT_CA:-./ssl/certs/ca.crt} + target: /etc/ssl/certs/ca.crt + - type: bind + source: ${MG_NGINX_SERVER_DHPARAM:-./ssl/dhparam.pem} + target: /etc/ssl/certs/dhparam.pem ports: - ${MG_NGINX_HTTP_PORT}:${MG_NGINX_HTTP_PORT} - ${MG_NGINX_SSL_PORT}:${MG_NGINX_SSL_PORT} @@ -716,7 +724,7 @@ services: MG_UI_DB_SSL_MODE: ${MG_UI_DB_SSL_MODE} MG_UI_DB_SSL_CERT: ${MG_UI_DB_SSL_CERT} MG_UI_DB_SSL_KEY: ${MG_UI_DB_SSL_KEY} - MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} + MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} ports: - ${MG_UI_PORT}:${MG_UI_PORT} networks: diff --git a/docker/nginx/entrypoint.sh b/docker/nginx/entrypoint.sh index 1076b600fc..6b90377035 100755 --- a/docker/nginx/entrypoint.sh +++ b/docker/nginx/entrypoint.sh @@ -12,6 +12,7 @@ else fi envsubst ' + ${MG_NGINX_SERVER_NAME} ${MG_AUTH_HTTP_PORT} ${MG_USERS_HTTP_PORT} ${MG_THINGS_HTTP_PORT} diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index aa0fa05edb..b33a0667b0 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -39,6 +39,14 @@ http { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; + set $dynamic_server_name "$MG_NGINX_SERVER_NAME"; + + if ($dynamic_server_name = '') { + set $dynamic_server_name "localhost"; + } + + server_name $dynamic_server_name; + include snippets/ssl.conf; add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; @@ -48,8 +56,6 @@ http { add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Headers '*'; - server_name localhost; - location ~ ^/(channels)/(.+)/(things)/(.+) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; diff --git a/docker/nginx/nginx-x509.conf b/docker/nginx/nginx-x509.conf index efed25da56..1c35f83e63 100644 --- a/docker/nginx/nginx-x509.conf +++ b/docker/nginx/nginx-x509.conf @@ -45,6 +45,15 @@ http { listen [::]:80 default_server; listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; + + set $dynamic_server_name "$MG_NGINX_SERVER_NAME"; + + if ($dynamic_server_name = '') { + set $dynamic_server_name "localhost"; + } + + server_name $dynamic_server_name; + ssl_verify_client optional; include snippets/ssl.conf; include snippets/ssl-client.conf; @@ -56,8 +65,6 @@ http { add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Headers '*'; - server_name localhost; - # Proxy pass to users service location ~ ^/(users|groups|password|policies|authorize) { include snippets/proxy-headers.conf; @@ -69,7 +76,7 @@ http { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}/policies; - } + } # Proxy pass to things service location ~ ^/(things|channels|connect|disconnect|identify) { @@ -77,7 +84,7 @@ http { add_header Access-Control-Expose-Headers Location; proxy_pass http://things:${MG_THINGS_HTTP_PORT}; } - + location ^~ /things/policies { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; @@ -90,7 +97,7 @@ http { add_header Access-Control-Expose-Headers Location; proxy_pass http://invitations:${MG_INVITATIONS_HTTP_PORT}; } - + location /health { include snippets/proxy-headers.conf; proxy_pass http://things:${MG_THINGS_HTTP_PORT}; @@ -100,7 +107,7 @@ http { include snippets/proxy-headers.conf; proxy_pass http://things:${MG_THINGS_HTTP_PORT}; } - + # Proxy pass to magistrala-http-adapter location /http/ { include snippets/verify-ssl-client.conf; diff --git a/go.mod b/go.mod index 69b3397290..ce6d068a69 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/gookit/color v1.5.4 github.com/gopcua/opcua v0.1.6 github.com/gorilla/websocket v1.5.1 - github.com/hashicorp/vault/api v1.10.0 + github.com/hashicorp/vault/api v1.12.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/influxdata/influxdb-client-go/v2 v2.13.0 github.com/ivanpirog/coloredcobra v1.0.1 @@ -54,8 +54,8 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 go.opentelemetry.io/otel/trace v1.22.0 - golang.org/x/crypto v0.18.0 - golang.org/x/net v0.20.0 + golang.org/x/crypto v0.19.0 + golang.org/x/net v0.21.0 golang.org/x/sync v0.6.0 gonum.org/v1/gonum v0.14.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac @@ -110,6 +110,7 @@ require ( github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/vault/api/auth/approle v0.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect @@ -174,7 +175,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/go.sum b/go.sum index 7ad59cad7d..5853168b52 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,7 @@ github.com/absmach/senml v1.0.5 h1:zNPRYpGr2Wsb8brAusz8DIfFqemy1a2dNbmMnegY3GE= github.com/absmach/senml v1.0.5/go.mod h1:NDEjk3O4V4YYu9Bs2/+t/AZ/F+0wu05ikgecp+/FsSU= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/authzed/authzed-go v0.10.1 h1:0aX2Ox9PPPknID92kLs/FnmhCmfl6Ni16v3ZTLsds5M= github.com/authzed/authzed-go v0.10.1/go.mod h1:ZsaFPCiMjwT0jLW0gCyYzh3elHqhKDDGGRySyykXwqc= github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403 h1:bQeIwWWRI9bl93poTqpix4sYHi+gnXUPK7N6bMtXzBE= @@ -33,6 +34,7 @@ github.com/authzed/grpcutil v0.0.0-20230908193239-4286bb1d6403/go.mod h1:s3qC7V7 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2ioR0= github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= @@ -41,7 +43,9 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -95,6 +99,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= @@ -179,6 +184,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -203,27 +209,38 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMW github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= +github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/hashicorp/vault/api/auth/approle v0.6.0 h1:ELfFFQlTM/e97WJKu1HvNFa7lQ3tlTwwzrR1NJE1V7Y= +github.com/hashicorp/vault/api/auth/approle v0.6.0/go.mod h1:CCoIl1xBC3lAWpd1HV+0ovk76Z8b8Mdepyk21h3pGk0= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -339,13 +356,18 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -355,8 +377,11 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -424,6 +449,7 @@ github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90/go.mod h1:Z7oKFLSG github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= @@ -447,6 +473,7 @@ github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTG github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -589,8 +616,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= @@ -630,8 +661,11 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= @@ -646,6 +680,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -657,6 +692,7 @@ golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -678,8 +714,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -687,6 +727,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -697,8 +739,10 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=