NOISSUE - Vault operations with app role authentication (#2084)

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: arvindh123 <arvindh91@gmail.com>
This commit is contained in:
Arvindh
2024-02-20 20:29:55 +05:30
committed by GitHub
parent 4c206ec375
commit ab4206cc93
23 changed files with 764 additions and 268 deletions
+41 -36
View File
@@ -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 | <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 | "" |
| 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 | <localhost:9000> |
| MG_JAEGER_URL | Jaeger server URL | <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 | "" |
| 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 \
+5
View File
@@ -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
+105 -4
View File
@@ -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))
}
}
}
+11 -5
View File
@@ -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())
+48 -19
View File
@@ -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
+15
View File
@@ -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.
+5 -4
View File
@@ -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}
+1
View File
@@ -2,3 +2,4 @@
# SPDX-License-Identifier: Apache-2.0
data
magistrala_things_certs_issue.hcl
+70 -32
View File
@@ -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`
### 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.
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.
This script should not be necessary to run after the initial setup, since the Vault service unseals itself when starting the container.
3. `vault-set-pki.sh`
### 4. `vault_set_pki.sh`
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.
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 CA parameters are obtained from the environment variables starting with `MG_VAULT_CA` in `.env` file.
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 is used to enable app role authorization in Vault. Certs service used the approle credentials to issue, revoke things certificate from vault intermedate CA.
`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`
### 6. `vault_copy_certs.sh`
This scripts copies the necessary certificates and keys from `docker/addons/vault/data` to the `docker/ssl/certs` folder.
## Vault CLI
@@ -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"]
}
-144
View File
@@ -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
+33
View File
@@ -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
@@ -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
+95
View File
@@ -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
+18
View File
@@ -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)
+199
View File
@@ -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
+13 -5
View File
@@ -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:
+1
View File
@@ -12,6 +12,7 @@ else
fi
envsubst '
${MG_NGINX_SERVER_NAME}
${MG_AUTH_HTTP_PORT}
${MG_USERS_HTTP_PORT}
${MG_THINGS_HTTP_PORT}
+8 -2
View File
@@ -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;
+13 -6
View File
@@ -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;
+5 -4
View File
@@ -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
+44
View File
@@ -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=