Skip to content

Commit

Permalink
feat(security): Create a Vault mgmt token for Consul Secrets API Oper…
Browse files Browse the repository at this point in the history
…ations (#3192)

* feat(security): Create a Vault mgmt token for Consul Secrets Operations

Changes in secretstore-setup:
 - add token file writer implementation for creating and writing Vault's Consul secrets admin token
 - add Consul ACL feature flag "ENABLE_REGISTRY_ACL"
 - add configuration setting for ConsulSecretAdminTokenPath for specifying token file location
 - refactor TokenMaintenance to share code with token file writer's implementation

Closes: #3155

Signed-off-by: Jim Wang <[email protected]>
  • Loading branch information
jim-wang-intel authored Feb 26, 2021
1 parent 73dc428 commit 257616a
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 94 deletions.
1 change: 1 addition & 0 deletions cmd/security-secretstore-setup/res/configuration.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ TokenProviderAdminTokenPath = "/run/edgex/secrets/tokenprovider/secrets-token.js
PasswordProvider = ""
PasswordProviderArgs = [ ]
RevokeRootTokens = true
ConsulSecretsAdminTokenPath = "/tmp/edgex/secrets/edgex-consul/admin/token.json"

[Databases]
[Databases.admin]
Expand Down
1 change: 1 addition & 0 deletions internal/security/secretstore/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type SecretStoreInfo struct {
PasswordProvider string
PasswordProviderArgs []string
RevokeRootTokens bool
ConsulSecretsAdminTokenPath string
}

// GetBaseURL builds and returns the base URL for the SecretStore service
Expand Down
6 changes: 2 additions & 4 deletions internal/security/secretstore/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
package secretstore

const (
// KVSecretsEngineMountPoint is the name of the mount point base for Vault's key-value secrets engine
KVSecretsEngineMountPoint = "secret"
// ConsulSecretEngineMountPoint is the name of the mount point base for Vault's Consul secrets engine
ConsulSecretEngineMountPoint = "consul"
// this is a feature key to indicate whether to enable Registry Consul's ACL or not
RegistryACLFeatureFlag = "ENABLE_REGISTRY_ACL"

VaultToken = "X-Vault-Token"
TokenCreatorPolicyName = "privileged-token-creator"
Expand Down
115 changes: 30 additions & 85 deletions internal/security/secretstore/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/edgexfoundry/edgex-go/internal/security/secretstore/config"
"github.com/edgexfoundry/edgex-go/internal/security/secretstore/container"
"github.com/edgexfoundry/edgex-go/internal/security/secretstore/secretsengine"
"github.com/edgexfoundry/edgex-go/internal/security/secretstore/tokenfilewriter"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/startup"
Expand Down Expand Up @@ -223,13 +224,11 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s
healthOkCh := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
if sCode, _ := client.HealthCheck(); sCode == http.StatusOK {
close(healthOkCh)
ticker.Stop()
return
}
<-ticker.C
if sCode, _ := client.HealthCheck(); sCode == http.StatusOK {
close(healthOkCh)
ticker.Stop()
return
}
}
}()
Expand Down Expand Up @@ -290,9 +289,10 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s

// If configured to do so, create a token issuing token
if secretStoreConfig.TokenProviderAdminTokenPath != "" {
revokeIssuingTokenFuc, err := makeTokenIssuingToken(lc, configuration, tokenMaintenance, fileOpener, rootToken)
revokeIssuingTokenFuc, err := tokenfilewriter.NewWriter(lc, client, fileOpener).
CreateAndWrite(rootToken, secretStoreConfig.TokenProviderAdminTokenPath, tokenMaintenance.CreateTokenIssuingToken)
if err != nil {
lc.Errorf("failed to create token issuing token %s", err.Error())
lc.Errorf("failed to create token issuing token: %s", err.Error())
os.Exit(1)
}
if secretStoreConfig.TokenProviderType == OneShotProvider {
Expand All @@ -318,19 +318,12 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, _ *sync.WaitGroup, _ s
}

// Enable KV secret engine
if err := secretsengine.New(KVSecretsEngineMountPoint, secretsengine.KeyValue).
if err := secretsengine.New(secretsengine.KVSecretsEngineMountPoint, secretsengine.KeyValue).
Enable(&rootToken, lc, client); err != nil {
lc.Errorf("failed to enable KV secrets engine: %s", err.Error())
os.Exit(1)
}

// Enable Consul secret engine
if err := secretsengine.New(ConsulSecretEngineMountPoint, secretsengine.Consul).
Enable(&rootToken, lc, client); err != nil {
lc.Errorf("failed to enable Consul secrets engine: %s", err.Error())
os.Exit(1)
}

// credential creation
gen := NewPasswordGenerator(lc, secretStoreConfig.PasswordProvider, secretStoreConfig.PasswordProviderArgs)
cred := NewCred(httpCaller, rootToken, gen, secretStoreConfig.GetBaseURL(), lc)
Expand Down Expand Up @@ -422,6 +415,26 @@ 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")
}

// If Registry Consul ACL feature is enabled, then we need to create and save a Vault token to configure
// Consul secret engine access, role operations, and managing Consul agent tokens.
registryACLEnabled := os.Getenv(RegistryACLFeatureFlag)
if registryACLEnabled == "true" {
// 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())
os.Exit(1)
}

// 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())
os.Exit(1)
}
}

lc.Info("Vault init done successfully")
return false

Expand Down Expand Up @@ -469,74 +482,6 @@ func addDBCredential(lc logger.LoggingClient, db string, cred Cred, service stri
return err
}

func makeTokenIssuingToken(
lc logger.LoggingClient,
configuration *config.ConfigurationStruct,
tokenMaintenance *TokenMaintenance,
fileOpener fileioperformer.FileIoPerformer,
rootToken string) (RevokeFunc, error) {

configAdminTokenPath := configuration.SecretStore.TokenProviderAdminTokenPath
if configAdminTokenPath == "" {
err := fmt.Errorf("TokenProviderAdminTokenPath is a required configuration setting")
lc.Error(err.Error())
return nil, err
}

// Create delegate credential for use by the token provider
tokenIssuingToken, revokeIssuingTokenFuc, err := tokenMaintenance.CreateTokenIssuingToken(rootToken)
if err != nil {
lc.Errorf("failed to create token issuing token %s", err.Error())
return nil, err
}
lc.Info("created token issuing token")

// Write the token issuing token to disk to pass it to the token provider
adminTokenPath, err := filepath.Abs(configAdminTokenPath)
if err != nil {
lc.Errorf("failed to convert to absolute path %s: %s", configAdminTokenPath, err.Error())
revokeIssuingTokenFuc()
return nil, err
}
dirOfAdminToken := filepath.Dir(adminTokenPath)
err = fileOpener.MkdirAll(dirOfAdminToken, 0700)
if err != nil {
lc.Errorf("failed to create tokenpath base dir: %s", err.Error())
revokeIssuingTokenFuc()
return nil, err
}
tokenWriter, err := fileOpener.OpenFileWriter(adminTokenPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
lc.Errorf("failed to create token issuing file %s: %s", adminTokenPath, err.Error())
revokeIssuingTokenFuc()
return nil, err
}

encoder := json.NewEncoder(tokenWriter)
if encoder == nil {
err := fmt.Errorf("failed to create token encoder")
lc.Error(err.Error())
_ = tokenWriter.Close()
revokeIssuingTokenFuc()
return nil, err
}

if err = encoder.Encode(tokenIssuingToken); err != nil {
lc.Errorf("failed to write token issuing token: %s", err.Error())
_ = tokenWriter.Close()
revokeIssuingTokenFuc()
return nil, err
}

if err = tokenWriter.Close(); err != nil {
lc.Errorf("failed to close token issuing file: %s", err.Error())
revokeIssuingTokenFuc()
return nil, err
}

return revokeIssuingTokenFuc, nil
}

func loadInitResponse(
lc logger.LoggingClient,
fileOpener fileioperformer.FileIoPerformer,
Expand Down
5 changes: 5 additions & 0 deletions internal/security/secretstore/secretsengine/enabler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import (
)

const (
// KVSecretsEngineMountPoint is the name of the mount point base for Vault'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
KeyValue = "kv"
Consul = "consul"
Expand Down
22 changes: 22 additions & 0 deletions internal/security/secretstore/tokencreatable/tokencreatable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*******************************************************************************
* Copyright 2021 Intel Corporation
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*******************************************************************************/

package tokencreatable

// RevokeFunc is a function to revoke a token
type RevokeFunc func()

// CreateTokenFunc is a function to create a token with optional revoke function in return
type CreateTokenFunc func(rootToken string) (createResponse map[string]interface{}, revokeFunc RevokeFunc, err error)
Loading

0 comments on commit 257616a

Please sign in to comment.