diff --git a/Attribution.txt b/Attribution.txt index 82c61997dc..6b9eaeff1a 100644 --- a/Attribution.txt +++ b/Attribution.txt @@ -98,22 +98,22 @@ https://bitbucket.org/bertimus9/systemstat/src/master/LICENSE davecgh/go-spew (ISC) https://github.com/davecgh/go-spew https://github.com/davecgh/go-spew/blob/master/LICENSE -edgexfoundry/go-mod-bootstrap (Apache 2.0) https://github.com/edgexfoundry/go-mod-bootstrap/v3 +edgexfoundry/go-mod-bootstrap (Apache 2.0) https://github.com/edgexfoundry/go-mod-bootstrap/v4 https://github.com/edgexfoundry/go-mod-bootstrap/blob/master/LICENSE -edgexfoundry/go-mod-configuration (Apache 2.0) https://github.com/edgexfoundry/go-mod-configuration/v3 +edgexfoundry/go-mod-configuration (Apache 2.0) https://github.com/edgexfoundry/go-mod-configuration/v4 https://github.com/edgexfoundry/go-mod-configuration/blob/master/LICENSE -edgexfoundry/go-mod-core-contracts (Apache 2.0) https://github.com/edgexfoundry/go-mod-core-contracts/v3 +edgexfoundry/go-mod-core-contracts (Apache 2.0) https://github.com/edgexfoundry/go-mod-core-contracts/v4 https://github.com/edgexfoundry/go-mod-core-contracts/blob/master/LICENSE -edgexfoundry/go-mod-messaging (Apache 2.0) https://github.com/edgexfoundry/go-mod-messaging/v3 +edgexfoundry/go-mod-messaging (Apache 2.0) https://github.com/edgexfoundry/go-mod-messaging/v4 https://github.com/edgexfoundry/go-mod-messaging/blob/master/LICENSE -edgexfoundry/go-mod-registry (Apache 2.0) https://github.com/edgexfoundry/go-mod-registry/v3 +edgexfoundry/go-mod-registry (Apache 2.0) https://github.com/edgexfoundry/go-mod-registry/v4 https://github.com/edgexfoundry/go-mod-registry/blob/master/LICENSE -edgexfoundry/go-mod-secrets (Apache 2.0) https://github.com/edgexfoundry/go-mod-secrets/v3 +edgexfoundry/go-mod-secrets (Apache 2.0) https://github.com/edgexfoundry/go-mod-secrets/v4 https://github.com/edgexfoundry/go-mod-secrets/blob/master/LICENSE gorilla/context (BSD-3) https://github.com/gorilla/context diff --git a/cmd/secrets-config/res/configuration.yaml b/cmd/secrets-config/res/configuration.yaml index cb8aedb9dc..16969e74e2 100644 --- a/cmd/secrets-config/res/configuration.yaml +++ b/cmd/secrets-config/res/configuration.yaml @@ -1,12 +1,13 @@ # # Copyright (c) 2023 Intel Corporation +# Copyright (c) 2024 IOTech Ltd # # SPDX-License-Identifier: Apache-2.0 # LogLevel: DEBUG SecretStore: - Type: vault + Type: openbao Protocol: http Host: localhost Port: 8200 @@ -14,9 +15,9 @@ SecretStore: CaFilePath: "" CertFilePath: "" KeyFilePath: "" - # for root token use: /vault/config/assets + # for root token use: /openbao/config/assets # for service token use: /tmp/edgex/secrets/security-proxy-setup - TokenFolderPath: /vault/config/assets + TokenFolderPath: /openbao/config/assets # for root token use: resp-init.json # for service token use: secrets-token.json TokenFile: resp-init.json diff --git a/cmd/security-bootstrapper/entrypoint-scripts/vault_wait_install.sh b/cmd/security-bootstrapper/entrypoint-scripts/secretstore_wait_install.sh similarity index 72% rename from cmd/security-bootstrapper/entrypoint-scripts/vault_wait_install.sh rename to cmd/security-bootstrapper/entrypoint-scripts/secretstore_wait_install.sh index f2a484015c..5ad82d8979 100755 --- a/cmd/security-bootstrapper/entrypoint-scripts/vault_wait_install.sh +++ b/cmd/security-bootstrapper/entrypoint-scripts/secretstore_wait_install.sh @@ -1,6 +1,7 @@ #!/usr/bin/dumb-init /bin/sh # ---------------------------------------------------------------------------------- # Copyright (c) 2021 Intel Corporation +# Copyright (c) 2024 IOTech Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,41 +18,41 @@ # SPDX-License-Identifier: Apache-2.0 # ---------------------------------------------------------------------------------- -# This is customized entrypoint script for Vault. +# This is customized entrypoint script for secret store. # In particular, it waits for the BootstrapPort ready to roll set -e # env settings are populated from env files of docker-compose -echo "Script for waiting security bootstrapping on Vault" +echo "Script for waiting security bootstrapping on Secret Store" -DEFAULT_VAULT_LOCAL_CONFIG=' +DEFAULT_BAO_LOCAL_CONFIG=' listener "tcp" { - address = "edgex-vault:8200" + address = "edgex-secret-store:8200" tls_disable = "1" - cluster_address = "edgex-vault:8201" + cluster_address = "edgex-secret-store:8201" } backend "file" { - path = "/vault/file" + path = "/openbao/file" } default_lease_ttl = "168h" max_lease_ttl = "720h" ' -VAULT_LOCAL_CONFIG=${VAULT_LOCAL_CONFIG:-$DEFAULT_VAULT_LOCAL_CONFIG} +BAO_LOCAL_CONFIG=${BAO_LOCAL_CONFIG:-$DEFAULT_BAO_LOCAL_CONFIG} -export VAULT_LOCAL_CONFIG +export BAO_LOCAL_CONFIG -echo "$(date) VAULT_LOCAL_CONFIG: ${VAULT_LOCAL_CONFIG}" +echo "$(date) BAO_LOCAL_CONFIG: ${BAO_LOCAL_CONFIG}" if [ "$1" = 'server' ]; then - echo "$(date) Executing waitFor on vault $* with \ + echo "$(date) Executing waitFor on secret store $* with \ tcp://${STAGEGATE_BOOTSTRAPPER_HOST}:${STAGEGATE_BOOTSTRAPPER_STARTPORT}" /edgex-init/security-bootstrapper --configDir=/edgex-init/res waitFor \ -uri tcp://"${STAGEGATE_BOOTSTRAPPER_HOST}":"${STAGEGATE_BOOTSTRAPPER_STARTPORT}" \ -timeout "${STAGEGATE_WAITFOR_TIMEOUT}" - echo "$(date) Starting edgex-vault..." + echo "$(date) Starting edgex-secret-store..." exec /usr/local/bin/docker-entrypoint.sh server -log-level=info fi diff --git a/cmd/security-bootstrapper/res/configuration.yaml b/cmd/security-bootstrapper/res/configuration.yaml index 7f61444701..b8d8180a39 100644 --- a/cmd/security-bootstrapper/res/configuration.yaml +++ b/cmd/security-bootstrapper/res/configuration.yaml @@ -61,7 +61,7 @@ StageGate: # protocol, host, and port of secretstore using in the security-bootstrapper # we are not really using the secret store provider from go-mod-bootstrap in the code SecretStore: - Type: vault + Type: openbao Protocol: http Host: localhost Port: 8200 diff --git a/cmd/security-proxy-setup/entrypoint.sh b/cmd/security-proxy-setup/entrypoint.sh index fee7d08f09..88d5c133b9 100644 --- a/cmd/security-proxy-setup/entrypoint.sh +++ b/cmd/security-proxy-setup/entrypoint.sh @@ -305,13 +305,13 @@ server { proxy_set_header Host \$host; } - # Note: Vault login API does not require authentication at the gateway for obvious reasons - set \$upstream_vault edgex-vault; + # Note: OpenBao login API does not require authentication at the gateway for obvious reasons + set \$upstream_secret_store edgex-secret-store; location /vault/v1/auth/userpass/login { `cat "${corssnippet}"` rewrite /vault/(.*) /\$1 break; resolver 127.0.0.11 valid=30s; - proxy_pass http://\$upstream_vault:8200; + proxy_pass http://\$upstream_secret_store:8200; proxy_redirect off; proxy_set_header Host \$host; } @@ -319,7 +319,7 @@ server { `cat "${corssnippet}"` rewrite /vault/(.*) /\$1 break; resolver 127.0.0.11 valid=30s; - proxy_pass http://\$upstream_vault:8200; + proxy_pass http://\$upstream_secret_store:8200; proxy_redirect off; proxy_set_header Host \$host; } diff --git a/cmd/security-secretstore-setup/README.md b/cmd/security-secretstore-setup/README.md index bb08629bfa..66817006ff 100644 --- a/cmd/security-secretstore-setup/README.md +++ b/cmd/security-secretstore-setup/README.md @@ -18,13 +18,13 @@ This will create an executable located at `cmd/security-secretstore-setup/` if s The binary supports multiple command line parameters -| Parameter | Description | -|-----------------------------------|----------------------------------------------------------------------------------------------------------------| -| -p, --profile `name` | Indicate configuration profile other than default | -| -r, --registry | Indicates service should use Registry | -| --insecureSkipVerify=`true/false` | Indicates if skipping the server side SSL cert verifcation, similar to -k of curl | -| --configfile=`file.yaml` | Use a different config file (default: res/configuration.yaml) | -| --vaultInterval=`seconds` | **Required** Indicates how long the program will pause between vault initialization attempts until it succeeds | +| Parameter | Description | +|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------| +| -p, --profile `name` | Indicate configuration profile other than default | +| -r, --registry | Indicates service should use Registry | +| --insecureSkipVerify=`true/false` | Indicates if skipping the server side SSL cert verifcation, similar to -k of curl | +| --configfile=`file.yaml` | Use a different config file (default: res/configuration.yaml) | +| --secretStoreInterval=`seconds` | **Required** Indicates how long the program will pause between secret store initialization attempts until it succeeds | An example of using the parameters can be found in the following docker compose file: @@ -50,19 +50,19 @@ It should create a docker image with the name `edgexfoundry/docker_security_secr RevokeRootTokens = false ``` -* The edgex-vault-worker uses _compose-files_vault-config_ volume to store its token. To copy the root token from edgex-vault-worker, use +* The edgex-vault-worker uses _compose-files_secret-store-config_ volume to store its token. To copy the root token from edgex-vault-worker, use ```sh - docker run --rm -v compose-files_vault-config:/vault/config alpine:latest cat /vault/config/assets/resp-init.json > resp-init.json + docker run --rm -v compose-secret-store-config:/openbao/config alpine:latest cat /openbao/config/assets/resp-init.json > resp-init.json ``` * To verify the root token ```sh - docker exec -ti edgex-vault sh -l + docker exec -ti edgex-secret-store sh -l export VAULT_SKIP_VERIFY=true export VAULT_TOKEN=s.xxxxxxxxxxxxxxxx - vault token lookup + bao token lookup ``` where `s.xxxxxxxxxxxxxxxx` is the _root_token_ member of `resp-init.json` @@ -72,19 +72,19 @@ It should create a docker image with the name `edgexfoundry/docker_security_secr * To explore the vault ```sh - docker exec -ti edgex-vault sh -l + docker exec -ti edgex-secret-store sh -l export VAULT_SKIP_VERIFY=true export VAULT_TOKEN=s.xxxxxxxxxxxxxxxx - vault kv list secret/ + bao kv list secret/ ``` - and drill down from there. To read a key use `vault kv get` or `vault read`. + and drill down from there. To read a key use `bao kv get` or `bao read`. ```sh - docker exec -ti edgex-vault sh -l + docker exec -ti edgex-secret-store sh -l export VAULT_SKIP_VERIFY=true export VAULT_TOKEN=s.xxxxxxxxxxxxxxxx - vault kv get /secret/edgex/redis/redis5 + bao kv get /secret/edgex/redis/redis5 ``` Note you can set the environment variables on the docker command line with `-e` and avoid the additional shell commands. diff --git a/cmd/security-secretstore-setup/entrypoint.sh b/cmd/security-secretstore-setup/entrypoint.sh index 6c9cfb6c38..141bc27704 100644 --- a/cmd/security-secretstore-setup/entrypoint.sh +++ b/cmd/security-secretstore-setup/entrypoint.sh @@ -27,11 +27,11 @@ if [ ! -z "${TOKENFILEPROVIDER_OUTPUTDIR}" ]; then fi # create token dir, and assign perms -mkdir -p /vault/config/assets -chown -Rh 100:1000 /vault/ +mkdir -p /openbao/config/assets +chown -Rh 100:1000 /openbao/ echo "Initializing secret store..." -/security-secretstore-setup --vaultInterval=10 +/security-secretstore-setup --secretStoreInterval=10 # default User and Group in case never set if [ -z "${EDGEX_USER}" ]; then diff --git a/cmd/security-secretstore-setup/res-file-token-provider/configuration.yaml b/cmd/security-secretstore-setup/res-file-token-provider/configuration.yaml index 6b2dd8e84e..96cd2b5f67 100644 --- a/cmd/security-secretstore-setup/res-file-token-provider/configuration.yaml +++ b/cmd/security-secretstore-setup/res-file-token-provider/configuration.yaml @@ -1,6 +1,6 @@ LogLevel: DEBUG SecretStore: - Type: vault + Type: openbao Protocol: http Host: localhost Port: 8200 diff --git a/cmd/security-secretstore-setup/res/configuration.yaml b/cmd/security-secretstore-setup/res/configuration.yaml index 9146e5243f..0b9769c623 100644 --- a/cmd/security-secretstore-setup/res/configuration.yaml +++ b/cmd/security-secretstore-setup/res/configuration.yaml @@ -16,7 +16,7 @@ LogLevel: DEBUG SecretStore: - Type: vault + Type: openbao Protocol: http Host: localhost Port: 8200 @@ -24,10 +24,10 @@ SecretStore: CaFilePath: "" CertFilePath: "" KeyFilePath: "" - TokenFolderPath: /vault/config/assets + TokenFolderPath: /openbao/config/assets TokenFile: resp-init.json - VaultSecretShares: 5 - VaultSecretThreshold: 3 + SecretShares: 5 + SecretThreshold: 3 TokenProvider: /security-file-token-provider TokenProviderArgs: - "-configDir" diff --git a/internal/security/bootstrapper/command/setupacl/command.go b/internal/security/bootstrapper/command/setupacl/command.go index 0ed90f7c79..4ee2847046 100644 --- a/internal/security/bootstrapper/command/setupacl/command.go +++ b/internal/security/bootstrapper/command/setupacl/command.go @@ -32,7 +32,6 @@ import ( "time" "github.com/edgexfoundry/edgex-go/internal" - "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/command/setupacl/share" "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/config" "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/helper" "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/interfaces" @@ -44,22 +43,19 @@ import ( "github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger" "github.com/edgexfoundry/go-mod-core-contracts/v4/common" "github.com/edgexfoundry/go-mod-secrets/v4/pkg" - "github.com/edgexfoundry/go-mod-secrets/v4/pkg/token/authtokenloader" "github.com/edgexfoundry/go-mod-secrets/v4/pkg/token/fileioperformer" "github.com/edgexfoundry/go-mod-secrets/v4/pkg/types" - "github.com/edgexfoundry/go-mod-secrets/v4/secrets" ) const ( // the command name for setting up registry's ACL CommandName string = "setupRegistryACL" - consulGetLeaderAPI = "/v1/status/leader" - consulACLBootstrapAPI = "/v1/acl/bootstrap" - consulConfigAccessVaultAPI = "/v1/consul/config/access" - consulLegacyACLModeError = "The ACL system is currently in legacy mode" - defaultRetryTimeout = 30 * time.Second - emptyLeader = `""` + consulGetLeaderAPI = "/v1/status/leader" + consulACLBootstrapAPI = "/v1/acl/bootstrap" + consulLegacyACLModeError = "The ACL system is currently in legacy mode" + defaultRetryTimeout = 30 * time.Second + emptyLeader = `""` // environment variable contains a comma separated list of registry role names to be added addRegistryRolesEnvKey = "EDGEX_ADD_REGISTRY_ACL_ROLES" @@ -74,7 +70,6 @@ type cmd struct { // internal state retryTimeout time.Duration bootstrapACLTokenCache *types.BootStrapACLTokenInfo - secretstoreTokenCache string } // NewCommand creates a new cmd and parses through options if any @@ -144,28 +139,6 @@ func (c *cmd) Execute() (statusCode int, err error) { return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to create bootstrap ACL token: %v", err) } - // retrieve the secretstore (Vault) token from the file produced by secretstore-setup - secretstoreToken, err := c.retrieveSecretStoreTokenFromFile() - if err != nil { - return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to retrieve secretstore token: %v", err) - } - - client, err := c.createSecretStoreClient(c.configuration) - if err != nil { - return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to create SecretStoreClient: %s", err.Error()) - } - //configure Consul access with both Secret Store token and consul's bootstrap acl token - if err := client.ConfigureConsulAccess(secretstoreToken, bootstrapACLToken.SecretID, - c.configuration.StageGate.Registry.Host, c.configuration.StageGate.Registry.Port); err != nil { - return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to configure Consul access: %v", err) - } - - c.loggingClient.Info("successfully get secretstore token and configuring the registry access for secretestore") - - if err := c.createEdgeXACLTokenRoles(bootstrapACLToken.SecretID, secretstoreToken); err != nil { - return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to createEdgeXACLTokenRoles: %v", err) - } - // set up agent token to agent for the first time if err := c.setupAgentToken(bootstrapACLToken); err != nil { return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to set up agent token: %v", err) @@ -200,11 +173,11 @@ func (c *cmd) reSetup() error { return fmt.Errorf("on 2nd time or later, failed to re-set up agent token: %v", err) } - // set up roles for both static and dynamic again in case there're changes - err := c.reSetupEdgeXACLTokenRoles() - if err != nil { - return fmt.Errorf("on 2nd time or later, failed to re-set up roles: %v", err) - } + //set up roles for both static and dynamic again in case there're changes + //err := c.reSetupEdgeXACLTokenRoles() + //if err != nil { + // return fmt.Errorf("on 2nd time or later, failed to re-set up roles: %v", err) + //} bootstrapACLToken, err := c.reconstructBootstrapACLToken() if err != nil { @@ -233,26 +206,6 @@ func (c *cmd) reSetup() error { return nil } -func (c *cmd) reSetupEdgeXACLTokenRoles() error { - bootstrapACLToken, err := c.reconstructBootstrapACLToken() - if err != nil { - return fmt.Errorf("failed to reconstruct bootstrap ACL token: %v", err) - } else if len(bootstrapACLToken.SecretID) == 0 { - return errors.New("bootstrapACLToken.SecretID is empty") - } - - secretstoreToken, err := c.getSecretStoreToken() - if err != nil { - return fmt.Errorf("failed to retrieve secretstore token: %v", err) - } - - if err := c.createEdgeXACLTokenRoles(bootstrapACLToken.SecretID, secretstoreToken); err != nil { - return fmt.Errorf("failed to create EdgeX roles: %v", err) - } - - return nil -} - func (c *cmd) createBootstrapACLToken() (*types.BootStrapACLTokenInfo, error) { bootstrapACLToken, err := c.generateBootStrapACLToken() if err != nil { @@ -306,82 +259,6 @@ func (c *cmd) createAndSaveManagementACLToken(bootstrapACLToken *types.BootStrap return nil } -// createEdgeXACLTokenRoles creates secret store roles that can be used for generating registry tokens -// via Consul secret engine API /consul/creds/[role_name] later on for all EdgeX microservices -func (c *cmd) createEdgeXACLTokenRoles(bootstrapACLTokenID, secretstoreToken string) error { - roleNames, err := c.getUniqueRoleNames() - if err != nil { - return fmt.Errorf("failed to get unique role names: %v", err) - } - - client, err := c.createSecretStoreClient(c.configuration) - if err != nil { - return fmt.Errorf("failed to create SecretStoreClient: %s", err.Error()) - } - // create registry roles for EdgeX - for roleName := range roleNames { - // create policy for each service role - servicePolicyRules := ` - # HCL definition of server agent policy for EdgeX - node "" { - policy = "read" - } - node_prefix "edgex" { - policy = "write" - } - service "` + roleName + `" { - policy = "write" - } - service_prefix "" { - policy = "read" - } - key_prefix "` + c.getKeyPrefix(roleName) + `" { - policy = "write" - } - key_prefix "` + c.getKeyPrefix(common.CoreCommonConfigServiceKey) + `" { - policy = "read" - } - ` - - edgexServicePolicy, err := c.getOrCreateRegistryPolicy(bootstrapACLTokenID, "acl_policy_for_"+roleName, servicePolicyRules) - if err != nil { - return fmt.Errorf("failed to create edgex service policy: %v", err) - } - - // create roles based on the service keys as the role names - edgexACLTokenRole := types.NewConsulRole(roleName, types.ClientType, []types.Policy{ - *edgexServicePolicy, - // localUse set to false as some EdgeX services may be running in a different node - }, false) - - if err := client.CreateRole(secretstoreToken, edgexACLTokenRole); err != nil { - return fmt.Errorf("failed to create edgex role: %v", err) - } - } - - return nil -} - -// getKeyPrefix get the consul ACL key prefix for the service with the input roleName, ie. the service key-based -// Currently we support 3 types of services: app services, device services, and security services -// if the input role name does not fall into the above types, then it is categorized into core type for the key prefix -func (c *cmd) getKeyPrefix(roleName string) string { - if strings.HasPrefix(roleName, "app-") { - return common.ConfigStemApp + "/" + roleName - } - - if strings.HasPrefix(roleName, "device-") { - return common.ConfigStemDevice + "/" + roleName - } - - if strings.HasPrefix(roleName, "security-") { - return common.ConfigStemSecurity + "/" + roleName - } - - // anything else falls into the 3rd category: core bucket - return common.ConfigStemCore + "/" + roleName -} - func (c *cmd) getUniqueRoleNames() (map[string]struct{}, error) { roleNamesFromConfig := c.configuration.StageGate.Registry.ACL.GetACLRoleNames() if len(roleNamesFromConfig) == 0 { @@ -626,48 +503,6 @@ func (c *cmd) getNonEmptyConsulLeader() error { } } -func (c *cmd) getSecretStoreToken() (string, error) { - if len(c.secretstoreTokenCache) > 0 { - return c.secretstoreTokenCache, nil - } - - if secretStoreToken, err := c.retrieveSecretStoreTokenFromFile(); err == nil { - c.secretstoreTokenCache = secretStoreToken - return secretStoreToken, nil - } else { - return share.EmptyToken, err - } -} - -func (c *cmd) retrieveSecretStoreTokenFromFile() (string, error) { - trimmedFilePath := strings.TrimSpace(c.configuration.StageGate.Registry.ACL.SecretsAdminTokenPath) - if len(trimmedFilePath) == 0 { - return share.EmptyToken, errors.New("required StageGate_Registry_ACL_SecretsAdminTokenPath from configuration is empty") - } - - tokenFileAbsPath, err := filepath.Abs(trimmedFilePath) - if err != nil { - return share.EmptyToken, fmt.Errorf("failed to convert tokenFile to absolute path %s: %v", trimmedFilePath, err) - } - - // since the secretstore token is created by another service, secretstore-setup, - // so here we want to make sure we have the file - if exists := helper.CheckIfFileExists(tokenFileAbsPath); !exists { - return share.EmptyToken, fmt.Errorf("secretstore token file %s not found", tokenFileAbsPath) - } - - fileOpener := fileioperformer.NewDefaultFileIoPerformer() - tokenLoader := authtokenloader.NewAuthTokenLoader(fileOpener) - secretStoreToken, err := tokenLoader.Load(tokenFileAbsPath) - if err != nil { - return share.EmptyToken, fmt.Errorf("tokenLoader failed to load secretstore token: %v", err) - } - - c.loggingClient.Infof("successfully retrieved secretstore management token from %s", trimmedFilePath) - - return secretStoreToken, nil -} - func (c *cmd) writeSentinelFile() error { absPath, err := filepath.Abs(c.configuration.StageGate.Registry.ACL.SentinelFilePath) if err != nil { @@ -692,19 +527,3 @@ func (c *cmd) writeSentinelFile() error { return nil } - -func (c *cmd) createSecretStoreClient(secretConfig *config.ConfigurationStruct) (secrets.SecretStoreClient, error) { - clientConfig := types.SecretConfig{ - Type: secrets.Vault, - Host: c.secretStoreinfo.Host, - Port: c.secretStoreinfo.Port, - Protocol: c.secretStoreinfo.Protocol, - } - - client, err := secrets.NewSecretStoreClient(clientConfig, c.loggingClient, c.client) - if err != nil { - return nil, fmt.Errorf("failed to create SecretStoreClient: %s", err.Error()) - } - - return client, nil -} diff --git a/internal/security/bootstrapper/command/setupacl/command_test.go b/internal/security/bootstrapper/command/setupacl/command_test.go index 540709f259..c5491111b7 100644 --- a/internal/security/bootstrapper/command/setupacl/command_test.go +++ b/internal/security/bootstrapper/command/setupacl/command_test.go @@ -109,10 +109,6 @@ func TestExecute(t *testing.T) { }, }}, httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) }, false, false, true}, - {"Bad:setupRegistryACL with timed out on waiting for secret token file", "", - prepareTestRegistryServer, true, false, true}, - {"Bad:setupRegistryACL with config access API failed response from server", "test5", - prepareTestRegistryServer, true, false, true}, } for _, tt := range tests { diff --git a/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go b/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go index b16f92297b..cfd58dfcc5 100644 --- a/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go +++ b/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go @@ -154,20 +154,6 @@ func (registry *registryTestServer) getRegistryServerConf(t *testing.T) *config. w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte("The ACL system is currently in legacy mode.")) } - case consulConfigAccessVaultAPI: - require.Equal(t, http.MethodPost, r.Method) - if registry.serverOptions.configAccessOkResponse { - w.WriteHeader(http.StatusNoContent) - } else { - w.WriteHeader(http.StatusForbidden) - } - case fmt.Sprintf("/v1/consul/roles/%s", pathBase): - require.Equal(t, http.MethodPost, r.Method) - if registry.serverOptions.createRoleOk { - w.WriteHeader(http.StatusNoContent) - } else { - w.WriteHeader(http.StatusForbidden) - } case consulCheckAgentAPI: require.Equal(t, http.MethodGet, r.Method) if registry.serverOptions.consulCheckAgentOk { diff --git a/internal/security/common/usermanager.go b/internal/security/common/usermanager.go index f74f18add1..722ec9fe44 100644 --- a/internal/security/common/usermanager.go +++ b/internal/security/common/usermanager.go @@ -47,7 +47,7 @@ func NewUserManager( } } -// CreatePasswordUserWithPolicy creates a vault identity with an attached policy +// CreatePasswordUserWithPolicy creates a secretstore identity with an attached policy // using userpass authentication engine. // username should be the name of the user or service to be created // password should be a random password to be assigned @@ -72,7 +72,7 @@ func (m *UserManager) CreatePasswordUserWithPolicy(username string, password str return err } - // Create or update underlying vault identity + // Create or update underlying secretstore identity identityMetadata := map[string]string{ // we will also put a name claim in any generated JWT's "name": username, diff --git a/internal/security/config/command/proxy/adduser/command.go b/internal/security/config/command/proxy/adduser/command.go index fa252e0a4b..6e1d32afbc 100644 --- a/internal/security/config/command/proxy/adduser/command.go +++ b/internal/security/config/command/proxy/adduser/command.go @@ -17,6 +17,7 @@ import ( secretStoreConfig "github.com/edgexfoundry/edgex-go/internal/security/secretstore/config" "github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger" + "github.com/edgexfoundry/go-mod-secrets/v4/secrets" ) const ( @@ -37,7 +38,7 @@ type cmd struct { jwtTTL string } -// NewCommand add a new user to Vault, which can then authenticate through the gateway +// NewCommand add a new user to OpenBao, which can then authenticate through the gateway func NewCommand( lc logger.LoggingClient, configuration *secretStoreConfig.ConfigurationStruct, @@ -62,7 +63,7 @@ func NewCommand( return nil, err } if cmd.username == "" { - return nil, fmt.Errorf("%s vault adduser: argument --user is required", os.Args[0]) + return nil, fmt.Errorf("%s %s adduser: argument --user is required", os.Args[0], secrets.DefaultSecretStore) } cmd.proxyUserCommon, err = shared.NewProxyUserCommon(lc, configuration) diff --git a/internal/security/config/command/proxy/adduser/command_test.go b/internal/security/config/command/proxy/adduser/command_test.go index 56e423e3ef..04fc697e8a 100644 --- a/internal/security/config/command/proxy/adduser/command_test.go +++ b/internal/security/config/command/proxy/adduser/command_test.go @@ -17,6 +17,7 @@ import ( "github.com/edgexfoundry/edgex-go/internal/security/secretstore/config" "github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger" + "github.com/edgexfoundry/go-mod-secrets/v4/secrets" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -108,7 +109,7 @@ func addUserWithArgs(t *testing.T, args []string) { p, _ := strconv.ParseInt(tsURL.Port(), 10, 32) config.SecretStore.Port = int(p) config.SecretStore.Protocol = "https" - config.SecretStore.Type = "vault" + config.SecretStore.Type = secrets.DefaultSecretStore config.SecretStore.TokenFolderPath = "testdata/" config.SecretStore.TokenFile = "token.json" diff --git a/internal/security/config/command/proxy/deluser/command_test.go b/internal/security/config/command/proxy/deluser/command_test.go index db97fb3f81..24c3144b0b 100644 --- a/internal/security/config/command/proxy/deluser/command_test.go +++ b/internal/security/config/command/proxy/deluser/command_test.go @@ -16,6 +16,7 @@ import ( "github.com/edgexfoundry/edgex-go/internal/security/config/interfaces" "github.com/edgexfoundry/edgex-go/internal/security/secretstore/config" "github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger" + "github.com/edgexfoundry/go-mod-secrets/v4/secrets" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -78,7 +79,7 @@ func delUserWithArgs(t *testing.T, args []string) { p, _ := strconv.ParseInt(tsURL.Port(), 10, 32) config.SecretStore.Port = int(p) config.SecretStore.Protocol = "https" - config.SecretStore.Type = "vault" + config.SecretStore.Type = secrets.DefaultSecretStore config.SecretStore.TokenFolderPath = "testdata/" config.SecretStore.TokenFile = "token.json" diff --git a/internal/security/config/command/proxy/shared/proxyuser.go b/internal/security/config/command/proxy/shared/proxyuser.go index 35320a79af..3aa937f4fd 100644 --- a/internal/security/config/command/proxy/shared/proxyuser.go +++ b/internal/security/config/command/proxy/shared/proxyuser.go @@ -45,7 +45,7 @@ type ProxyUserCommon struct { secretStoreClient secrets.SecretStoreClient } -// NewProxyUserCommon has common logic for adding and deleting users from Vault +// NewProxyUserCommon has common logic for adding and deleting users from OpenBao func NewProxyUserCommon( lc logger.LoggingClient, configuration *secretStoreConfig.ConfigurationStruct) (ProxyUserCommon, error) { @@ -92,7 +92,7 @@ func NewProxyUserCommon( return vb, err } -// LoadServiceToken loads a vault token from SecretStore.TokenFile (secrets-token.json) +// LoadServiceToken loads a token from SecretStore.TokenFile (secrets-token.json) func (vb *ProxyUserCommon) LoadServiceToken() (string, func(), error) { // This is not a root token; don't need to revoke when we're done with it @@ -112,7 +112,7 @@ func (vb *ProxyUserCommon) LoadServiceToken() (string, func(), error) { } -// LoadRootToken regenerates a temporary root token from Vault keyshares +// LoadRootToken regenerates a temporary root token from OpenBao keyshares func (vb *ProxyUserCommon) LoadRootToken() (string, func(), error) { pipedHexReader := pipedhexreader.NewPipedHexReader() keyDeriver := kdf.NewKdf(vb.fileOpener, vb.configuration.SecretStore.TokenFolderPath, sha256.New) @@ -123,12 +123,12 @@ func (vb *ProxyUserCommon) LoadRootToken() (string, func(), error) { err := vmkEncryption.LoadIKM(hook) defer vmkEncryption.WipeIKM() // Ensure IKM is wiped from memory if err != nil { - vb.loggingClient.Errorf("failed to setup vault master key encryption: %s", err.Error()) + vb.loggingClient.Errorf("failed to setup %s master key encryption: %s", secrets.DefaultSecretStore, err.Error()) return "", nil, err } - vb.loggingClient.Info("Enabled encryption of Vault master key") + vb.loggingClient.Infof("Enabled encryption of %s master key", secrets.DefaultSecretStore) } else { - vb.loggingClient.Info("vault master key encryption not enabled. EDGEX_IKM_HOOK not set.") + vb.loggingClient.Infof("%s master key encryption not enabled. EDGEX_IKM_HOOK not set.", secrets.DefaultSecretStore) } var initResponse types.InitResponse diff --git a/internal/security/fileprovider/init.go b/internal/security/fileprovider/init.go index 5dcc58b09a..9c695e87bf 100644 --- a/internal/security/fileprovider/init.go +++ b/internal/security/fileprovider/init.go @@ -65,7 +65,7 @@ func (b *Bootstrap) BootstrapHandler(_ context.Context, _ *sync.WaitGroup, _ sta } clientConfig := types.SecretConfig{ - Type: secrets.Vault, + Type: secrets.DefaultSecretStore, Host: cfg.SecretStore.Host, Port: cfg.SecretStore.Port, Protocol: cfg.SecretStore.Protocol, diff --git a/internal/security/proxyauth/init.go b/internal/security/proxyauth/init.go index 3700008f4f..ababfbe83b 100644 --- a/internal/security/proxyauth/init.go +++ b/internal/security/proxyauth/init.go @@ -51,7 +51,7 @@ func NewBootstrap(router *echo.Echo, serviceName string) *Bootstrap { func (b *Bootstrap) BootstrapHandler(ctx context.Context, wg *sync.WaitGroup, _ startup.Timer, dic *di.Container) bool { lc := container.LoggingClientFrom(dic.Get) secretProvider := container.SecretProviderExtFrom(dic.Get) - authenticationHook := handlers.VaultAuthenticationHandlerFunc(secretProvider, lc) + authenticationHook := handlers.SecretStoreAuthenticationHandlerFunc(secretProvider, lc) // Common _ = controller.NewCommonController(dic, b.router, b.serviceName, edgex.Version) diff --git a/internal/security/secretstore/config/config.go b/internal/security/secretstore/config/config.go index ce0e07f6d0..4cf5db998d 100644 --- a/internal/security/secretstore/config/config.go +++ b/internal/security/secretstore/config/config.go @@ -59,8 +59,8 @@ type SecretStoreInfo struct { KeyFilePath string TokenFolderPath string TokenFile string - VaultSecretShares int - VaultSecretThreshold int + SecretShares int + SecretThreshold int TokenProvider string TokenProviderArgs []string TokenProviderType string diff --git a/internal/security/secretstore/init.go b/internal/security/secretstore/init.go index 2e8fdf93ca..c8a3f328ef 100644 --- a/internal/security/secretstore/init.go +++ b/internal/security/secretstore/init.go @@ -1,7 +1,7 @@ /******************************************************************************* * Copyright 2022-2023 Intel Corporation * Copyright 2019 Dell Inc. - * Copyright (C) 2024 IOTech Ltd + * Copyright 2024 IOTech Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -69,16 +69,16 @@ const ( var errNotFound = errors.New("credential NOT found") type Bootstrap struct { - insecureSkipVerify bool - vaultInterval int - validKnownSecrets map[string]bool + insecureSkipVerify bool + secretStoreInterval int + validKnownSecrets map[string]bool } -func NewBootstrap(insecureSkipVerify bool, vaultInterval int) *Bootstrap { +func NewBootstrap(insecureSkipVerify bool, secretStoreInterval int) *Bootstrap { return &Bootstrap{ - insecureSkipVerify: insecureSkipVerify, - vaultInterval: vaultInterval, - validKnownSecrets: map[string]bool{redisSecretName: true, messagebusSecretName: true}, + insecureSkipVerify: insecureSkipVerify, + secretStoreInterval: secretStoreInterval, + validKnownSecrets: map[string]bool{redisSecretName: true, messagebusSecretName: true}, } } @@ -107,7 +107,7 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s httpCaller = pkg.NewRequester(lc).Insecure() } - intervalDuration := time.Duration(b.vaultInterval) * time.Second + intervalDuration := time.Duration(b.secretStoreInterval) * time.Second clientConfig := types.SecretConfig{ Type: secretStoreConfig.Type, Protocol: secretStoreConfig.Protocol, @@ -131,17 +131,17 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s err := vmkEncryption.LoadIKM(hook) defer vmkEncryption.WipeIKM() // Ensure IKM is wiped from memory if err != nil { - lc.Errorf("failed to setup vault master key encryption: %s", err.Error()) + lc.Errorf("failed to setup %s master key encryption: %s", secrets.DefaultSecretStore, err.Error()) return false } - lc.Info("Enabled encryption of Vault master key") + lc.Infof("Enabled encryption of %s master key", secrets.DefaultSecretStore) } else { - lc.Info("vault master key encryption not enabled. EDGEX_IKM_HOOK not set.") + lc.Infof("%s master key encryption not enabled. EDGEX_IKM_HOOK not set.", secrets.DefaultSecretStore) } var initResponse types.InitResponse // reused many places in below flow - //step 3: initialize and unseal Vault + //step 3: initialize and unseal secret store for shouldContinue := true; shouldContinue; { // Anonymous function used to prevent file handles from accumulating terminalFailure := func() bool { @@ -154,7 +154,7 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s lc.Errorf("unable to load init response: %s", err.Error()) return true } - lc.Infof("vault is initialized and unsealed (status code: %d)", sCode) + lc.Infof("%s is initialized and unsealed (status code: %d)", secrets.DefaultSecretStore, sCode) shouldContinue = false case http.StatusTooManyRequests: @@ -162,10 +162,10 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s shouldContinue = false case http.StatusNotImplemented: - lc.Infof("vault is not initialized (status code: %d). Starting initialization and unseal phases", sCode) - initResponse, err = client.Init(secretStoreConfig.VaultSecretThreshold, secretStoreConfig.VaultSecretShares) + lc.Infof("%s is not initialized (status code: %d). Starting initialization and unseal phases", secrets.DefaultSecretStore, sCode) + initResponse, err = client.Init(secretStoreConfig.SecretThreshold, secretStoreConfig.SecretShares) if err != nil { - lc.Errorf("Unable to Initialize Vault: %s. Will try again...", err.Error()) + lc.Errorf("Unable to Initialize %s: %s. Will try again...", secrets.DefaultSecretStore, err.Error()) // Not terminal failure, should continue and try again return false } @@ -178,14 +178,14 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s err = client.Unseal(initResponse.KeysBase64) if err != nil { - lc.Errorf("Unable to unseal Vault: %s", err.Error()) + lc.Errorf("Unable to unseal %s: %s", secrets.DefaultSecretStore, err.Error()) return true } // We need the unencrypted initResponse in order to generate a temporary root token later // Make a copy and save the copy, possibly encrypted encryptedInitResponse := initResponse - // Optionally encrypt the vault init response based on whether encryption was enabled + // Optionally encrypt the secret store init response based on whether encryption was enabled if vmkEncryption.IsEncrypting() { if err := vmkEncryption.EncryptInitResponse(&encryptedInitResponse); err != nil { lc.Errorf("failed to encrypt init response from secret store: %s", err.Error()) @@ -198,12 +198,12 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s } case http.StatusServiceUnavailable: - lc.Infof("vault is sealed (status code: %d). Starting unseal phase", sCode) + lc.Infof("%s is sealed (status code: %d). Starting unseal phase", secrets.DefaultSecretStore, sCode) if err := LoadInitResponse(lc, fileOpener, secretStoreConfig, &initResponse); err != nil { lc.Errorf("unable to load init response: %s", err.Error()) return true } - // Optionally decrypt the vault init response based on whether encryption was enabled + // Optionally decrypt the secret store init response based on whether encryption was enabled if vmkEncryption.IsEncrypting() { err = vmkEncryption.DecryptInitResponse(&initResponse) if err != nil { @@ -219,9 +219,9 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s default: if sCode == 0 { - lc.Errorf("vault is in an unknown state. No Status code available") + lc.Errorf("%s is in an unknown state. No Status code available", secrets.DefaultSecretStore) } else { - lc.Errorf("vault is in an unknown state. Status code: %d", sCode) + lc.Errorf("%s is in an unknown state. Status code: %d", secrets.DefaultSecretStore, sCode) } } @@ -233,12 +233,12 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s } if shouldContinue { - lc.Infof("trying Vault init/unseal again in %d seconds", b.vaultInterval) + lc.Infof("trying %s init/unseal again in %d seconds", secrets.DefaultSecretStore, b.secretStoreInterval) time.Sleep(intervalDuration) } } - /* After vault is initialized and unsealed, it takes a while to get ready to accept any request. During which period any request will get http 500 error. + /* After secret store is initialized and unsealed, it takes a while to get ready to accept any request. During which period any request will get http 500 error. We need to check the status constantly until it return http StatusOK. */ ticker := time.NewTicker(time.Second) @@ -329,7 +329,7 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s lc.Errorf("failed to check if %s auth method enabled: %s", UserPassAuthEngine, err.Error()) return false } else if !upAuthEnabled { - // Enable userpass engine at /v1/auth/{eng.path} path (/v1 prefix supplied by Vault) + // Enable userpass engine at /v1/auth/{eng.path} path (/v1 prefix supplied by secret store) lc.Infof("Enabling userpass authentication for the first time...") if err := client.EnablePasswordAuth(rootToken, UPAuthMountPoint); err != nil { lc.Errorf("failed to enable userpass secrets engine: %s", err.Error()) @@ -389,7 +389,7 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s // continue credential creation // A little note on why there are two secrets names. For each microservice, the redis - // username/password is uploaded to the vault on both /v1/secret/edgex/%s/redisdb and + // username/password is uploaded to the secret store on both /v1/secret/edgex/%s/redisdb and // /v1/secret/edgex/redisdb/%s). The go-mod-secrets client requires a SecretName property to prefix all // secrets. // So edgex/%s/redisdb is for the microservices (microservices are restricted to their specific @@ -590,24 +590,7 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s lc.Info("proxy certificate pair upload was skipped because cert secretStore value(s) were blank") } - // create and save a Vault token to configure - // Consul secret engine access, role operations, and managing Consul agent tokens. - // Enable Consul secret engine - if err := secretsengine.New(secretsengine.ConsulSecretEngineMountPoint, secretsengine.Consul). - Enable(&rootToken, lc, client); err != nil { - lc.Errorf("failed to enable Consul secrets engine: %s", err.Error()) - return false - } - - // generate a management token for Consul secrets engine operations: - tokenFileWriter := tokenfilewriter.NewWriter(lc, client, fileOpener) - if _, err := tokenFileWriter.CreateAndWrite(rootToken, configuration.SecretStore.ConsulSecretsAdminTokenPath, - tokenFileWriter.CreateMgmtTokenForConsulSecretsEngine); err != nil { - lc.Errorf("failed to create and write the token for Consul secret management: %s", err.Error()) - return false - } - - lc.Info("Vault init done successfully") + lc.Infof("%s init done successfully", secrets.DefaultSecretStore) return true } diff --git a/internal/security/secretstore/main.go b/internal/security/secretstore/main.go index bf57070b7b..02a479d071 100644 --- a/internal/security/secretstore/main.go +++ b/internal/security/secretstore/main.go @@ -1,6 +1,7 @@ /******************************************************************************* * Copyright 2019 Dell Inc. * Copyright 2023 Intel Corporation + * Copyright 2024 IOTech Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -56,7 +57,7 @@ func Main(ctx context.Context, cancel context.CancelFunc) { } f.FlagSet.BoolVar(&insecureSkipVerify, "insecureSkipVerify", false, "") - f.FlagSet.IntVar(&vaultInterval, "vaultInterval", 30, "") + f.FlagSet.IntVar(&secretStoreInterval, "secretStoreInterval", 30, "") f.Parse(os.Args[1:]) configuration := &config.ConfigurationStruct{} @@ -79,7 +80,7 @@ func Main(ctx context.Context, cancel context.CancelFunc) { false, bootstrapConfig.ServiceTypeOther, []interfaces.BootstrapHandler{ - NewBootstrap(insecureSkipVerify, vaultInterval).BootstrapHandler, + NewBootstrap(insecureSkipVerify, secretStoreInterval).BootstrapHandler, }, ) diff --git a/internal/security/secretstore/secretsengine/enabler.go b/internal/security/secretstore/secretsengine/enabler.go index 9045bfe0ac..3f809f2be8 100644 --- a/internal/security/secretstore/secretsengine/enabler.go +++ b/internal/security/secretstore/secretsengine/enabler.go @@ -1,5 +1,6 @@ /******************************************************************************* * Copyright 2021-2023 Intel Corporation +* Copyright (C) 2024 IOTech Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -23,23 +24,15 @@ import ( ) const ( - // KVSecretsEngineMountPoint is the name of the mount point base for Vault's key-value secrets engine + // KVSecretsEngineMountPoint is the name of the mount point base for OpenBao's key-value secrets engine KVSecretsEngineMountPoint = "secret" - // ConsulSecretEngineMountPoint is the name of the mount point base for Vault's Consul secrets engine - ConsulSecretEngineMountPoint = "consul" - // Vault's secrets engine type related constants + // OpenBao's secrets engine type related constants KeyValue = "kv" - Consul = "consul" // kvVersion is the version of key-value secret storage used - // currently we use version 1 from Vault + // currently we use version 1 from OpenBao kvVersion = "1" - - // defaultConsulTokenLeaseTtl is the default time-to-live value for consul token - // currently we don't set any lease time-to-live limit for Consul tokens - // this will be changed in future for phase 3 based on the ADR - defaultConsulTokenLeaseTtl = "0" ) // SecretsEngine is the metadata for secretstore secret engine enabler @@ -63,7 +56,7 @@ func (eng SecretsEngine) Enable(rootToken *string, return fmt.Errorf("rootToken is required") } - // the data returned from GET of check installed secrets engine API of Vault is + // the data returned from GET of check installed secrets engine API of OpenBao is // the mountPoint with trailing slash(/), eg. "secret/" for kv's mountPoint "secret" checkMountPoint := eng.mountPoint + "/" installed, err := client.CheckSecretEngineInstalled(*rootToken, checkMountPoint, eng.engineType) @@ -76,18 +69,11 @@ func (eng SecretsEngine) Enable(rootToken *string, lc.Infof("enabling %s secrets engine for the first time...", eng.engineType) switch eng.engineType { case KeyValue: - // Enable KV storage version 1 at /v1/{eng.path} path (/v1 prefix supplied by Vault) + // Enable KV storage version 1 at /v1/{eng.path} path (/v1 prefix supplied by OpenBao) if err := client.EnableKVSecretEngine(*rootToken, eng.mountPoint, kvVersion); err != nil { return fmt.Errorf("failed to enable KV version %s secrets engine: %w", kvVersion, err) } lc.Infof("KeyValue secrets engine with version %s enabled", kvVersion) - case Consul: - // Enable Consul secrets storage at /consul path - if err := client.EnableConsulSecretEngine(*rootToken, - eng.mountPoint, defaultConsulTokenLeaseTtl); err != nil { - return fmt.Errorf("failed to enable Consul secrets engine: %w", err) - } - lc.Infof("Consul secrets engine with config default_ttl = %s enabled", defaultConsulTokenLeaseTtl) default: return fmt.Errorf("Unsupported secrets engine type: %s", eng.engineType) } diff --git a/internal/security/secretstore/secretsengine/enabler_test.go b/internal/security/secretstore/secretsengine/enabler_test.go index 2d9d460910..706baf3008 100644 --- a/internal/security/secretstore/secretsengine/enabler_test.go +++ b/internal/security/secretstore/secretsengine/enabler_test.go @@ -1,5 +1,6 @@ /******************************************************************************* * Copyright 2021-2023 Intel Corporation + * Copyright (C) 2024 IOTech Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -33,7 +34,6 @@ func TestNewSecretsEngine(t *testing.T) { engineType string }{ {"New kv type of secrets engine", "kv-1-test/", KeyValue}, - {"New consul type of secrets engine", "consul-test/", Consul}, } for _, tt := range tests { @@ -56,40 +56,25 @@ func TestEnableSecretsEngine(t *testing.T) { mountPoint string engineType string kvInstalled bool - consulInstalled bool clientCallFailed bool expectError bool }{ {"Ok:Enable kv secrets engine not installed yet with client call ok", &testToken, "kv-1-test", - KeyValue, false, false, false, false}, - {"Ok:Enable consul secrets engine not installed yet with client call ok", &testToken, "consul-test", - Consul, false, false, false, false}, + KeyValue, false, false, false}, {"Ok:Enable kv secrets engine already installed with client call ok (1)", &testToken, "kv-1-test", - KeyValue, true, false, false, false}, - {"Ok:Enable consul secrets engine already installed with client call ok (1)", &testToken, "consul-test", - Consul, false, true, false, false}, + KeyValue, true, false, false}, {"Ok:Enable kv secrets engine already installed with client call ok (2)", &testToken, "kv-1-test", - KeyValue, true, true, false, false}, - {"Ok:Enable consul secrets engine already installed with client call ok (2)", &testToken, "consul-test", - Consul, true, true, false, false}, + KeyValue, true, false, false}, {"Bad:Enable kv secrets engine not installed yet but client call failed", &testToken, "kv-1-test", - KeyValue, false, false, true, true}, - {"Bad:Enable consul secrets engine not installed yet but client call failed", &testToken, "consul-test", - Consul, false, false, true, true}, + KeyValue, false, true, true}, {"Bad:Enable kv secrets engine already installed but client call failed (1)", &testToken, "kv-1-test", - KeyValue, true, false, true, true}, - {"Bad:Enable consul secrets engine already installed but client call failed (1)", &testToken, "consul-test", - Consul, false, true, true, true}, + KeyValue, true, true, true}, {"Bad:Enable kv secrets engine already installed but client call failed (2)", &testToken, "kv-1-test", - KeyValue, true, true, true, true}, - {"Bad:Enable consul secrets engine already installed but client call failed (2)", &testToken, "consul-test", - Consul, true, true, true, true}, + KeyValue, true, true, true}, {"Bad:Enable kv secrets engine with nil token", nil, "kv-1-test", - KeyValue, false, true, false, true}, - {"Bad:Enable consul secrets engine with nil token", nil, "consul-test", - Consul, true, false, false, true}, + KeyValue, false, false, true}, {"Bad:Unsupported secrets engine type", &testToken, "whatever", - "unsupported", false, false, false, true}, + "unsupported", false, false, true}, } for _, test := range tests { @@ -111,18 +96,12 @@ func TestEnableSecretsEngine(t *testing.T) { mockClient := &mocks.SecretStoreClient{} mockClient.On("CheckSecretEngineInstalled", mock.Anything, mock.Anything, KeyValue). Return(localTest.kvInstalled, chkErr) - mockClient.On("CheckSecretEngineInstalled", mock.Anything, mock.Anything, Consul). - Return(localTest.consulInstalled, chkErr) mockClient.On("CheckSecretEngineInstalled", mock.Anything, mock.Anything, mock.Anything). Return(false, chkErr) mockClient.On("EnableKVSecretEngine", mock.Anything, localTest.mountPoint, kvVersion). Return(enableClientErr) mockClient.On("EnableKVSecretEngine", mock.Anything, mock.Anything, mock.Anything). Return(unsupportedEngTypeErr) - mockClient.On("EnableConsulSecretEngine", mock.Anything, localTest.mountPoint, defaultConsulTokenLeaseTtl). - Return(enableClientErr) - mockClient.On("EnableConsulSecretEngine", mock.Anything, mock.Anything, mock.Anything). - Return(unsupportedEngTypeErr) err := New(localTest.mountPoint, localTest.engineType). Enable(localTest.rootToken, lc, mockClient) diff --git a/internal/security/secretstore/tokenfilewriter/tokenfilewriter.go b/internal/security/secretstore/tokenfilewriter/tokenfilewriter.go index 2c3f873e8c..21fb4ec91a 100644 --- a/internal/security/secretstore/tokenfilewriter/tokenfilewriter.go +++ b/internal/security/secretstore/tokenfilewriter/tokenfilewriter.go @@ -1,5 +1,6 @@ /******************************************************************************* * Copyright 2021 Intel Corporation + * Copyright (C) 2024 IOTech Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -31,10 +32,6 @@ import ( "github.com/edgexfoundry/go-mod-secrets/v4/secrets" ) -const ( - consulSecretsEngineOpsPolicyName = "consul_secrets_engine_management_policy" -) - // TokenFileWriter is a mechanism to generates a token and writes it into a file specified by configuration type TokenFileWriter struct { logClient logger.LoggingClient @@ -111,56 +108,6 @@ func (w TokenFileWriter) CreateAndWrite(rootToken string, tokenFilePath string, return revokeTokenFunc, nil } -// CreateMgmtTokenForConsulSecretsEngine creates a new Vault token that -// allows the Consul bootstrapper to operate on managing Vault's Consul secrets engine related APIs (see reference: -// https://www.vaultproject.io/api-docs/secret/consul). The created Vault token is meant for serving -// the purpose of Consul ACL's bootstrapping as part of securing Consul process. -// -// Requires a root token to create, and returns data/information containing the token, -// keeping the token without revoking it and hence always returning nil RevokeFunc in order to conform to the -// input type tokencreatable.CreateTokenFunc as its function argument; -// this function returns non-nil error if anything goes wrong during the creation. -// this function conforms to the signature of the tokencreatable.CreateTokenFunc type -// so that it can be passed to CreateAndWrite() -func (w TokenFileWriter) CreateMgmtTokenForConsulSecretsEngine(rootToken string) (map[string]interface{}, - tokencreatable.RevokeFunc, error) { - consulSecretsEngineOpsPolicyDocument := ` -# allow to configure the access information for Consul -path "` + secretsengine.ConsulSecretEngineMountPoint + `/config/access" { - capabilities = ["create", "update"] -} - -# allow to create, update, read, list, or delete the Consul role definition -path "` + secretsengine.ConsulSecretEngineMountPoint + `/roles/*" { - capabilities = ["create", "read", "update", "delete", "list"] -} -` - - if err := w.secretClient.InstallPolicy(rootToken, - consulSecretsEngineOpsPolicyName, - consulSecretsEngineOpsPolicyDocument); err != nil { - return nil, nil, fmt.Errorf("failed to install Consul secrets engine operations policy: %v", err) - } - - // setup new token's properties - tokenParams := make(map[string]interface{}) - tokenParams["type"] = "service" - // Vault prefixes "token" in front of display_name - tokenParams["display_name"] = "for Consul ACL bootstrap" - tokenParams["no_parent"] = true - tokenParams["period"] = "1h" - tokenParams["policies"] = []string{consulSecretsEngineOpsPolicyName} - tokenParams["meta"] = map[string]interface{}{ - "description": "Consul secrets engine management token", - } - response, err := w.secretClient.CreateToken(rootToken, tokenParams) - if err != nil { - return nil, nil, fmt.Errorf("failed to create token for Consul secrets engine operations: %v", err) - } - - return response, nil, nil -} - func getFunctionName(f interface{}) string { createTokenFuncName := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() // On runtime, this will get us something like: diff --git a/internal/security/secretstore/tokenfilewriter/tokenfilewriter_test.go b/internal/security/secretstore/tokenfilewriter/tokenfilewriter_test.go index 9c4fbdc1cd..94cbc7aa7f 100644 --- a/internal/security/secretstore/tokenfilewriter/tokenfilewriter_test.go +++ b/internal/security/secretstore/tokenfilewriter/tokenfilewriter_test.go @@ -40,138 +40,3 @@ func TestNewTokenFileWriter(t *testing.T) { tokenFileWriter := NewWriter(lc, sc, flOpener) require.NotEmpty(t, tokenFileWriter) } - -func TestCreateMgmtTokenForConsulSecretsEngine(t *testing.T) { - createTokenResult := getCreateTokenResultStub() - - testRootToken := "testRootToken" - testInstallPolicyErr := errors.New("install policy error") - testCreateTokenErr := errors.New("create token error") - - tests := []struct { - name string - installPolicyError error - createTokenError error - }{ - {"Ok:create token ok", nil, nil}, - {"Bad:install policy error", testInstallPolicyErr, nil}, - {"Bad:create token error", nil, testCreateTokenErr}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // setup mock secretclient and expected return values for this test - secretClient := &mocks.SecretStoreClient{} - - secretClient.On("InstallPolicy", testRootToken, - consulSecretsEngineOpsPolicyName, mock.Anything). - Return(test.installPolicyError).Once() - - secretClient.On("CreateToken", testRootToken, mock.Anything). - Return(createTokenResult, test.createTokenError).Once() - - token, _, err := NewWriter(lc, secretClient, flOpener). - CreateMgmtTokenForConsulSecretsEngine(testRootToken) - - if test.installPolicyError != nil || test.createTokenError != nil { - require.Error(t, err) - } else { - require.NoError(t, err) - require.Equal(t, "createdTestToken", token["auth"].(map[string]interface{})["client_token"]) - secretClient.AssertExpectations(t) - } - }) - } -} - -func TestCreateAndWriteForConsulSecretEngine(t *testing.T) { - createTokenResult := getCreateTokenResultStub() - - testInstallPolicyErr := errors.New("install policy error") - testCreateTokenErr := errors.New("create token error") - testTokenFileDir := "test" - testRootToken := "testRootToken" - - tests := []struct { - name string - rootToken string - installPolicyCallErr error - createTokenCallErr error - }{ - {"Ok:CreateAndWrite with client call ok", testRootToken, nil, nil}, - {"Bad:CreateAndWrite with empty token", "", nil, nil}, - {"Bad:CreateAndWrite with install policy call error", testRootToken, testInstallPolicyErr, nil}, - {"Bad:CreateAndWrite with create token call error", testRootToken, nil, testCreateTokenErr}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // setup mock secretclient and expected return values for this test - secretClient := &mocks.SecretStoreClient{} - - secretClient.On("InstallPolicy", testRootToken, - consulSecretsEngineOpsPolicyName, mock.Anything). - Return(test.installPolicyCallErr).Once() - - secretClient.On("CreateToken", testRootToken, mock.Anything). - Return(createTokenResult, test.createTokenCallErr).Once() - - testTokenFilePath := filepath.Join(testTokenFileDir, - "mgmt-token.json"+"_"+strconv.FormatInt(time.Now().UnixNano(), 10)) - fileWriter := NewWriter(lc, secretClient, flOpener) - _, err := fileWriter.CreateAndWrite(test.rootToken, testTokenFilePath, - fileWriter.CreateMgmtTokenForConsulSecretsEngine) - defer func() { - _ = os.RemoveAll(filepath.Dir(testTokenFilePath)) - }() - - expectError := test.installPolicyCallErr != nil || - test.createTokenCallErr != nil || - len(test.rootToken) == 0 - - if expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - require.FileExists(t, testTokenFilePath) - } - }) - } -} - -func TestFileWriteErrorForConsulSecretEngine(t *testing.T) { - curUser, _ := user.Current() - if curUser != nil && curUser.Uid == "0" { - // it is root user then we skip this test as root can have permission to write everything - t.Log("Skipping this test as it is running as root user") - t.Skip() - } - - createTokenResult := getCreateTokenResultStub() - - secretClient := &mocks.SecretStoreClient{} - testRootToken := "testRootToken" - - secretClient.On("InstallPolicy", testRootToken, - consulSecretsEngineOpsPolicyName, mock.Anything). - Return(nil).Once() - - secretClient.On("CreateToken", testRootToken, mock.Anything). - Return(createTokenResult, nil).Once() - - // literally set to the /root/ directory so that there is no write permission - // as only the root user can perform this - testTokenFileDir := "/root/test" - testTokenFilePath := filepath.Join(testTokenFileDir, "mgmt-token.json") - fileWriter := NewWriter(lc, secretClient, flOpener) - _, err := fileWriter.CreateAndWrite(testRootToken, testTokenFilePath, - fileWriter.CreateMgmtTokenForConsulSecretsEngine) - require.Error(t, err) -} - -func getCreateTokenResultStub() map[string]interface{} { - createTokenResult := make(map[string]interface{}) - createTokenResult["auth"] = make(map[string]interface{}) - createTokenResult["auth"].(map[string]interface{})["client_token"] = "createdTestToken" - return createTokenResult -} diff --git a/internal/security/secretstore/vmkencryption.go b/internal/security/secretstore/vmkencryption.go index 223aa3fe99..0df02540d5 100644 --- a/internal/security/secretstore/vmkencryption.go +++ b/internal/security/secretstore/vmkencryption.go @@ -20,6 +20,7 @@ import ( "github.com/edgexfoundry/edgex-go/internal/security/pipedhexreader" "github.com/edgexfoundry/go-mod-secrets/v4/pkg/token/fileioperformer" "github.com/edgexfoundry/go-mod-secrets/v4/pkg/types" + "github.com/edgexfoundry/go-mod-secrets/v4/secrets" ) /* @@ -27,13 +28,13 @@ import ( THE FOLLOWING DISCLAIMER IS REQUIRED TO BE CARRIED WITH THE BELOW CODE DO NOT REMOVE EXCEPT BY PERMISSION OF THE AUTHOR -Vault Master Key Encryption Feature +OpenBao Master Key Encryption Feature -The purpose of this feature is to provide a secure way to unlock Vault -without requiring human intervention to supply the Vault Master Key. +The purpose of this feature is to provide a secure way to unlock OpenBao +without requiring human intervention to supply the OpenBao Master Key. This feature requires a sufficiently secure source of randomness in order -to unlock the Vault. This randomness should have at least 256 bits of entropy +to unlock the Openbao. This randomness should have at least 256 bits of entropy and be backed by hardware secure storage. If using a TPM, the secret should be bound to platform configuration registers to attest the system state. @@ -187,16 +188,16 @@ func (v *VMKEncryption) DecryptInitResponse(initResp *types.InitResponse) error // gcmEncryptKeyShare encrypts each key share with a unique key // from the key derivation function based on passing the info -// string vault0, vault1, ... et cetera to the KDF. +// string secretstore, secretstore, ... et cetera to the KDF. func (v *VMKEncryption) gcmEncryptKeyShare(keyShare []byte, counter int) ([]byte, []byte, error) { defer wipeKey(keyShare) // wipe original keyShare on exit - info := fmt.Sprintf("vault%d", counter) + info := fmt.Sprintf("secretstore%d", counter) key, err := v.kdf.DeriveKey(v.ikm, aesKeyLength, info) if err != nil { - return nil, nil, fmt.Errorf("failed to derive encryption key for vault master key share %w", err) + return nil, nil, fmt.Errorf("failed to derive encryption key for %s master key share %w", secrets.DefaultSecretStore, err) } defer wipeKey(key) // wipe encryption key on exit @@ -223,16 +224,16 @@ func (v *VMKEncryption) gcmEncryptKeyShare(keyShare []byte, counter int) ([]byte // gcmDecryptKeyShare decrypts each key share with a unique key // from the key derivation function based on passing the info -// string vault0, vault1, ... et cetera to the KDF. +// string secretstore0, secretstore1, ... et cetera to the KDF. func (v *VMKEncryption) gcmDecryptKeyShare(keyShare []byte, nonce []byte, counter int) ([]byte, error) { defer wipeKey(keyShare) // wipe original (encrypted) key share on exit (not technically needed) - info := fmt.Sprintf("vault%d", counter) + info := fmt.Sprintf("secretstore%d", counter) key, err := v.kdf.DeriveKey(v.ikm, aesKeyLength, info) if err != nil { - return nil, fmt.Errorf("failed to derive encryption key for vault master key share %w", err) + return nil, fmt.Errorf("failed to derive encryption key for %s master key share %w", secrets.DefaultSecretStore, err) } defer wipeKey(key) // wipe encryption key on exit diff --git a/internal/security/secretstore/vmkencryption_test.go b/internal/security/secretstore/vmkencryption_test.go index 69ec4b32f4..1ad7f6c4fe 100644 --- a/internal/security/secretstore/vmkencryption_test.go +++ b/internal/security/secretstore/vmkencryption_test.go @@ -46,8 +46,8 @@ func TestVMKEncryption(t *testing.T) { pipedHexReader := &MockPipedHexReader{} pipedHexReader.On("ReadHexBytesFromExe", "/bin/myikm").Return(fakeIkm, nil) kdf := &MockKeyDeriver{} - kdf.On("DeriveKey", make([]byte, 512), uint(32), "vault0").Return(make([]byte, 32), nil) - kdf.On("DeriveKey", make([]byte, 512), uint(32), "vault1").Return(make([]byte, 32), nil) + kdf.On("DeriveKey", make([]byte, 512), uint(32), "secretstore0").Return(make([]byte, 32), nil) + kdf.On("DeriveKey", make([]byte, 512), uint(32), "secretstore1").Return(make([]byte, 32), nil) initialInitResp := types.InitResponse{ Keys: []string{"aabbcc", "ddeeff"}, KeysBase64: []string{"qrvM", "3e7/"}, diff --git a/openapi/core-data.yaml b/openapi/core-data.yaml index 93daa20322..8f618a19b5 100644 --- a/openapi/core-data.yaml +++ b/openapi/core-data.yaml @@ -1887,13 +1887,13 @@ paths: MaxResultCount: 50000 Timeout: "5s" SecretStore: - Host: "edgex-vault" + Host: "edgex-secret-store" Port: 8200 Path: "v1/secret/edgex/core-data/" Protocol: "http" Namespace: "" RootCaCertPath: "" - ServerName: "edgex-vault" + ServerName: "edgex-secret-store" Authentication: AuthType: "X-Vault-Token" AuthToken: "" diff --git a/openapi/core-keeper.yaml b/openapi/core-keeper.yaml index 3202676ca8..ed3aaada56 100644 --- a/openapi/core-keeper.yaml +++ b/openapi/core-keeper.yaml @@ -860,13 +860,13 @@ paths: MaxResultCount: 50000 Timeout: 5000 SecretStore: - Host: "edgex-vault" + Host: "edgex-secret-store" Port: 8200 Path: "v1/secret/edgex/core-metadata/" Protocol: "http" Namespace: "" RootCaCertPath: "" - ServerName: "edgex-vault" + ServerName: "edgex-secret-store" Authentication: AuthType: "X-Vault-Token" AuthToken: "" diff --git a/openapi/core-metadata.yaml b/openapi/core-metadata.yaml index eb7db62f2e..99d50c1dc3 100644 --- a/openapi/core-metadata.yaml +++ b/openapi/core-metadata.yaml @@ -4231,13 +4231,13 @@ paths: MaxResultCount: 50000 Timeout: "5s" SecretStore: - Host: "edgex-vault" + Host: "edgex-secret-store" Port: 8200 Path: "v1/secret/edgex/core-metadata/" Protocol: "http" Namespace: "" RootCaCertPath: "" - ServerName: "edgex-vault" + ServerName: "edgex-secret-store" Authentication: AuthType: "X-Vault-Token" AuthToken: ""