diff --git a/internal/security/bootstrapper/command/setupacl/aclbootstrap.go b/internal/security/bootstrapper/command/setupacl/aclbootstrap.go index 9ac51a66d6..93f713ad78 100644 --- a/internal/security/bootstrapper/command/setupacl/aclbootstrap.go +++ b/internal/security/bootstrapper/command/setupacl/aclbootstrap.go @@ -25,22 +25,11 @@ import ( "path/filepath" "github.com/edgexfoundry/go-mod-secrets/v2/pkg/token/fileioperformer" + "github.com/edgexfoundry/go-mod-secrets/v2/pkg/types" ) -// BootStrapACLTokenInfo is the key portion of the response metadata from consulACLBootstrapAPI -type BootStrapACLTokenInfo struct { - SecretID string `json:"SecretID"` - Policies []Policy `json:"Policies"` -} - -// Policy is the metadata for ACL policy -type Policy struct { - ID string `json:"ID"` - Name string `json:"Name"` -} - // generateBootStrapACLToken should only be called once per Consul agent -func (c *cmd) generateBootStrapACLToken() (*BootStrapACLTokenInfo, error) { +func (c *cmd) generateBootStrapACLToken() (*types.BootStrapACLTokenInfo, error) { aclBootstrapURL, err := c.getRegistryApiUrl(consulACLBootstrapAPI) if err != nil { return nil, err @@ -64,7 +53,7 @@ func (c *cmd) generateBootStrapACLToken() (*BootStrapACLTokenInfo, error) { return nil, fmt.Errorf("Failed to read response body of bootstrap ACL: %w", err) } - var bootstrapACLToken BootStrapACLTokenInfo + var bootstrapACLToken types.BootStrapACLTokenInfo switch resp.StatusCode { case http.StatusOK: if err := json.NewDecoder(bytes.NewReader(responseBody)).Decode(&bootstrapACLToken); err != nil { @@ -77,7 +66,7 @@ func (c *cmd) generateBootStrapACLToken() (*BootStrapACLTokenInfo, error) { } } -func (c *cmd) saveBootstrapACLToken(tokenInfoToBeSaved *BootStrapACLTokenInfo) error { +func (c *cmd) saveBootstrapACLToken(tokenInfoToBeSaved *types.BootStrapACLTokenInfo) error { // Write the token to the specified file tokenFileAbsPath, err := filepath.Abs(c.configuration.StageGate.Registry.ACL.BootstrapTokenPath) if err != nil { diff --git a/internal/security/bootstrapper/command/setupacl/aclpolicies.go b/internal/security/bootstrapper/command/setupacl/aclpolicies.go index 569a842f62..854822bf18 100644 --- a/internal/security/bootstrapper/command/setupacl/aclpolicies.go +++ b/internal/security/bootstrapper/command/setupacl/aclpolicies.go @@ -25,6 +25,7 @@ import ( "github.com/edgexfoundry/edgex-go/internal/security/bootstrapper/command/setupacl/share" "github.com/edgexfoundry/go-mod-core-contracts/v2/common" + "github.com/edgexfoundry/go-mod-secrets/v2/pkg/types" ) const ( @@ -108,7 +109,7 @@ const ( // getOrCreateRegistryPolicy retrieves or creates a new policy // it inserts a new policy if the policy name does not exist and returns a policy // it returns the same policy if the policy name already exists -func (c *cmd) getOrCreateRegistryPolicy(tokenID, policyName, policyRules string) (*Policy, error) { +func (c *cmd) getOrCreateRegistryPolicy(tokenID, policyName, policyRules string) (*types.Policy, error) { // try to get the policy to see if it exists or not policy, err := c.getPolicyByName(tokenID, policyName) if err != nil { @@ -165,7 +166,7 @@ func (c *cmd) getOrCreateRegistryPolicy(tokenID, policyName, policyRules string) return nil, fmt.Errorf("Failed to read create a new policy response body: %w", err) } - var created Policy + var created types.Policy switch resp.StatusCode { case http.StatusOK: @@ -183,7 +184,7 @@ func (c *cmd) getOrCreateRegistryPolicy(tokenID, policyName, policyRules string) } // getPolicyByName gets policy by policy name, returns nil if not found -func (c *cmd) getPolicyByName(tokenID, policyName string) (*Policy, error) { +func (c *cmd) getPolicyByName(tokenID, policyName string) (*types.Policy, error) { readPolicyByNameURL, err := c.getRegistryApiUrl(fmt.Sprintf(consulReadPolicyByNameAPI, policyName)) if err != nil { return nil, err @@ -211,7 +212,7 @@ func (c *cmd) getPolicyByName(tokenID, policyName string) (*Policy, error) { switch resp.StatusCode { case http.StatusOK: - var existing Policy + var existing types.Policy if err := json.NewDecoder(bytes.NewReader(readPolicyResp)).Decode(&existing); err != nil { return nil, fmt.Errorf("failed to decode Policy json data: %v", err) } diff --git a/internal/security/bootstrapper/command/setupacl/aclroles.go b/internal/security/bootstrapper/command/setupacl/aclroles.go deleted file mode 100644 index 8e84e2dc3a..0000000000 --- a/internal/security/bootstrapper/command/setupacl/aclroles.go +++ /dev/null @@ -1,139 +0,0 @@ -/******************************************************************************* - * 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 setupacl - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strings" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/common" -) - -// RegistryTokenType is the type of registry tokens that will be created when the role is using to call token creds API -type RegistryTokenType string - -const ( - /* - * The following are available registry token types that can be used for specifying in the role-based tokens - * created via /consul/creds secret engine Vault API. - * For the details, see reference https://www.vaultproject.io/api/secret/consul#create-update-role - */ - // ManagementType is the type of registry role can be used to create tokens when role-based API /consul/creds is called - // the management type of created tokens is automatically granted the built-in global management policy - ManagementType RegistryTokenType = "management" - // ClientType is the type of registry role that can be used to create tokens when role-based API /consul/creds is called - // the regular client type of created tokens is associated with custom policies - ClientType RegistryTokenType = "client" - - createConsulRoleVaultAPI = "/v1/consul/roles/%s" -) - -// RegistryRole is the meta definition for creating registry's role -type RegistryRole struct { - RoleName string `json:"name"` - TokenType string `json:"token_type"` - PolicyNames []string `json:"policies,omitempty"` - Local bool `json:"local,omitempty"` - TimeToLive string `json:"TTL,omitempty"` -} - -// NewRegistryRole instantiates a new RegistryRole with the given inputs -func NewRegistryRole(name string, tokenType RegistryTokenType, policies []Policy, localUse bool) RegistryRole { - // to conform to the payload of the registry create role API, - // we convert the slice of policies from type Policy to string and make it unique - // as the policy name needs to be unique per API's requirement - policyNames := make([]string, 0, len(policies)) - tempMap := make(map[string]bool) - for _, policy := range policies { - if _, exists := tempMap[policy.Name]; !exists { - policyNames = append(policyNames, policy.Name) - } - } - - return RegistryRole{ - RoleName: strings.TrimSpace(name), - TokenType: string(tokenType), - PolicyNames: policyNames, - Local: localUse, - // unlimited for now - TimeToLive: "0s", - } -} - -// createRole creates a secret store role that can be used to generate registry tokens -// and part of elements for the role ties up with the registry policies in which it dictates -// the permission of accesses to the registry kv store or agent etc. -func (c *cmd) createRole(secretStoreToken string, registryRole RegistryRole) error { - if len(secretStoreToken) == 0 { - return errors.New("required secret store token is empty") - } - - if len(registryRole.RoleName) == 0 { - return errors.New("required role name cannot be empty") - } - - createRoleURL := fmt.Sprintf("%s://%s:%d%s", c.configuration.SecretStore.Protocol, - c.configuration.SecretStore.Host, c.configuration.SecretStore.Port, - fmt.Sprintf(createConsulRoleVaultAPI, registryRole.RoleName)) - _, err := url.Parse(createRoleURL) - if err != nil { - return fmt.Errorf("failed to parse create role URL: %v", err) - } - - c.loggingClient.Debugf("createRoleURL: %s", createRoleURL) - - jsonPayload, err := json.Marshal(®istryRole) - - if err != nil { - return fmt.Errorf("failed to marshal JSON string payload: %v", err) - } - - req, err := http.NewRequest(http.MethodPost, createRoleURL, bytes.NewBuffer(jsonPayload)) - if err != nil { - return fmt.Errorf("failed to prepare POST request for http URL: %w", err) - } - - req.Header.Add("X-Vault-Token", secretStoreToken) - req.Header.Add(common.ContentType, common.ContentTypeJSON) - resp, err := c.client.Do(req) - if err != nil { - return fmt.Errorf("failed to send request for http URL: %w", err) - } - - defer func() { - _ = resp.Body.Close() - }() - - switch resp.StatusCode { - case http.StatusNoContent: - // no response body returned in this case - c.loggingClient.Infof("successfully created a role [%s] for secretstore", registryRole.RoleName) - return nil - default: - body, err := io.ReadAll(resp.Body) - if err != nil { - c.loggingClient.Errorf("cannot read resp.Body: %v", err) - } - return fmt.Errorf("failed to create a role %s for secretstore via URL [%s] and status code= %d: %s", - registryRole.RoleName, createRoleURL, resp.StatusCode, string(body)) - } -} diff --git a/internal/security/bootstrapper/command/setupacl/aclroles_test.go b/internal/security/bootstrapper/command/setupacl/aclroles_test.go deleted file mode 100644 index 49696e4854..0000000000 --- a/internal/security/bootstrapper/command/setupacl/aclroles_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/******************************************************************************* - * 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 setupacl - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" -) - -func TestCreateRole(t *testing.T) { - ctx := context.Background() - wg := &sync.WaitGroup{} - lc := logger.MockLogger{} - testSecretStoreToken := "test-secretstore-token" - testSinglePolicy := []Policy{ - { - ID: "test-ID", - Name: "test-name", - }, - } - testMultiplePolicies := []Policy{ - { - ID: "test-ID1", - Name: "test-name1", - }, - { - ID: "test-ID2", - Name: "test-name2", - }, - } - - testRoleWithNilPolicy := NewRegistryRole("testRoleSingle", ClientType, nil, true) - testRoleWithEmptyPolicy := NewRegistryRole("testRoleSingle", ClientType, []Policy{}, true) - testRoleWithSinglePolicy := NewRegistryRole("testRoleSingle", ClientType, testSinglePolicy, true) - testRoleWithMultiplePolicies := NewRegistryRole("testRoleMultiple", ClientType, testMultiplePolicies, true) - testEmptyRoleName := NewRegistryRole("", ManagementType, testSinglePolicy, true) - - tests := []struct { - name string - secretstoreToken string - registryRole RegistryRole - creatRoleOkResponse bool - expectedErr bool - }{ - {"Good:create role with single policy ok", testSecretStoreToken, testRoleWithSinglePolicy, true, false}, - {"Good:create role with multiple policies ok", testSecretStoreToken, testRoleWithMultiplePolicies, true, false}, - {"Good:create role with empty policy ok", testSecretStoreToken, testRoleWithEmptyPolicy, true, false}, - {"Good:create role with nil policy ok", testSecretStoreToken, testRoleWithNilPolicy, true, false}, - {"Bad:create role bad response", testSecretStoreToken, testRoleWithSinglePolicy, false, true}, - {"Bad:empty secretstore token", "", testRoleWithMultiplePolicies, false, true}, - {"Bad:empty role name", testSecretStoreToken, testEmptyRoleName, false, true}, - } - - for _, tt := range tests { - test := tt // capture as local copy - t.Run(test.name, func(t *testing.T) { - t.Parallel() - // prepare test - responseOpts := serverOptions{ - createRoleOk: test.creatRoleOkResponse, - } - testSrv := newRegistryTestServer(responseOpts) - conf := testSrv.getRegistryServerConf(t) - defer testSrv.close() - - command, err := NewCommand(ctx, wg, lc, conf, []string{}) - require.NoError(t, err) - require.NotNil(t, command) - require.Equal(t, "setupRegistryACL", command.GetCommandName()) - setupRegistryACL := command.(*cmd) - setupRegistryACL.retryTimeout = 2 * time.Second - - err = setupRegistryACL.createRole(test.secretstoreToken, test.registryRole) - - if test.expectedErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/internal/security/bootstrapper/command/setupacl/acltokens.go b/internal/security/bootstrapper/command/setupacl/acltokens.go index aacc87419d..d729ad0287 100644 --- a/internal/security/bootstrapper/command/setupacl/acltokens.go +++ b/internal/security/bootstrapper/command/setupacl/acltokens.go @@ -32,6 +32,7 @@ import ( "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/startup" "github.com/edgexfoundry/go-mod-core-contracts/v2/common" "github.com/edgexfoundry/go-mod-secrets/v2/pkg/token/fileioperformer" + "github.com/edgexfoundry/go-mod-secrets/v2/pkg/types" ) // AgentTokenType is the type of token to be set on the Consul agent @@ -62,28 +63,28 @@ const ( // CreateRegistryToken is the structure to create a new registry token type CreateRegistryToken struct { - Description string `json:"Description"` - Policies []Policy `json:"Policies"` - Local bool `json:"Local"` - TTL *string `json:"ExpirationTTL,omitempty"` + Description string `json:"Description"` + Policies []types.Policy `json:"Policies"` + Local bool `json:"Local"` + TTL *string `json:"ExpirationTTL,omitempty"` } // ACLTokenInfo is the key portion of the response metadata from consulCreateTokenAPI type ACLTokenInfo struct { - SecretID string `json:"SecretID"` - AccessorID string `json:"AccessorID"` - Policies []Policy `json:"Policies"` - Description string `json:"Description"` + SecretID string `json:"SecretID"` + AccessorID string `json:"AccessorID"` + Policies []types.Policy `json:"Policies"` + Description string `json:"Description"` } // ManagementACLTokenInfo is the key portion of the response metadata from consulCreateTokenAPI for management acl token type ManagementACLTokenInfo struct { - SecretID string `json:"SecretID"` - Policies []Policy `json:"Policies"` + SecretID string `json:"SecretID"` + Policies []types.Policy `json:"Policies"` } // NewCreateRegistryToken instantiates a new CreateRegistryToken with a given inputs -func NewCreateRegistryToken(description string, policies []Policy, local bool, timeToLive *string) CreateRegistryToken { +func NewCreateRegistryToken(description string, policies []types.Policy, local bool, timeToLive *string) CreateRegistryToken { return CreateRegistryToken{ Description: description, Policies: policies, @@ -156,7 +157,7 @@ func (c *cmd) isACLTokenPersistent(bootstrapACLToken string) (bool, error) { // this call requires ACL read/write permission and hence we use the bootstrap ACL token // it checks whether there is an agent token already existing and re-uses it if so // otherwise creates a new agent token -func (c *cmd) createAgentToken(bootstrapACLToken BootStrapACLTokenInfo) (string, error) { +func (c *cmd) createAgentToken(bootstrapACLToken types.BootStrapACLTokenInfo) (string, error) { if len(bootstrapACLToken.SecretID) == 0 { return share.EmptyToken, errors.New("bootstrap ACL token is required for creating agent token") } @@ -208,7 +209,7 @@ func (c *cmd) createAgentToken(bootstrapACLToken BootStrapACLTokenInfo) (string, // getEdgeXTokenByPattern lists tokens and find the matched ACL Token info that contains token by the expected key pattern // it returns the first matched ACL Token info that contains token if many tokens actually are matched // it returns empty ACLTokenInfo if no matching found -func (c *cmd) getEdgeXTokenByPattern(bootstrapACLToken BootStrapACLTokenInfo, pattern *regexp.Regexp) (*ACLTokenInfo, error) { +func (c *cmd) getEdgeXTokenByPattern(bootstrapACLToken types.BootStrapACLTokenInfo, pattern *regexp.Regexp) (*ACLTokenInfo, error) { listTokensURL, err := c.getRegistryApiUrl(consulListTokensAPI) if err != nil { return nil, err @@ -267,7 +268,7 @@ func (c *cmd) getEdgeXTokenByPattern(bootstrapACLToken BootStrapACLTokenInfo, pa // insertNewAgentToken creates a new Consul token // it returns the token's ID and error if any error occurs -func (c *cmd) insertNewAgentToken(bootstrapACLToken BootStrapACLTokenInfo) (string, error) { +func (c *cmd) insertNewAgentToken(bootstrapACLToken types.BootStrapACLTokenInfo) (string, error) { // get a policy for this agent token to associate with edgexAgentPolicy, err := c.getOrCreateRegistryPolicy(bootstrapACLToken.SecretID, "edgex-agent-policy", @@ -278,7 +279,7 @@ func (c *cmd) insertNewAgentToken(bootstrapACLToken BootStrapACLTokenInfo) (stri unlimitedDuration := "0s" createToken := NewCreateRegistryToken("edgex-core-consul agent token", - []Policy{ + []types.Policy{ *edgexAgentPolicy, }, true, &unlimitedDuration) newTokenInfo, err := c.createNewToken(bootstrapACLToken.SecretID, createToken) @@ -291,7 +292,7 @@ func (c *cmd) insertNewAgentToken(bootstrapACLToken BootStrapACLTokenInfo) (stri } // setAgentToken sets the ACL token currently in use by the agent -func (c *cmd) setAgentToken(bootstrapACLToken BootStrapACLTokenInfo, agentTokenID string, +func (c *cmd) setAgentToken(bootstrapACLToken types.BootStrapACLTokenInfo, agentTokenID string, tokenType AgentTokenType) error { if len(bootstrapACLToken.SecretID) == 0 { return errors.New("bootstrap ACL token is required for setting agent token") @@ -407,7 +408,7 @@ func (c *cmd) createNewToken(bootstrapACLTokenID string, createToken CreateRegis // insertNewManagementToken creates a new Consul token // it returns the ACLTokenInfo and error if any error occurs -func (c *cmd) insertNewManagementToken(bootstrapACLToken BootStrapACLTokenInfo) (*ACLTokenInfo, error) { +func (c *cmd) insertNewManagementToken(bootstrapACLToken types.BootStrapACLTokenInfo) (*ACLTokenInfo, error) { // get a policy for this consul token to associate with edgexManagementPolicy, err := c.getOrCreateRegistryPolicy(bootstrapACLToken.SecretID, edgeXManagementPolicyName, @@ -418,7 +419,7 @@ func (c *cmd) insertNewManagementToken(bootstrapACLToken BootStrapACLTokenInfo) unlimitedDuration := "0s" createToken := NewCreateRegistryToken("edgex-core-consul management token", - []Policy{ + []types.Policy{ *edgexManagementPolicy, }, true, &unlimitedDuration) newTokenInfo, err := c.createNewToken(bootstrapACLToken.SecretID, createToken) @@ -435,7 +436,7 @@ func (c *cmd) insertNewManagementToken(bootstrapACLToken BootStrapACLTokenInfo) // this call requires ACL read/write permission and hence we use the bootstrap ACL token // it checks whether there is an management token already existing and re-uses it if so // otherwise creates a new management token -func (c *cmd) createManagementToken(bootstrapACLToken BootStrapACLTokenInfo) (*ManagementACLTokenInfo, error) { +func (c *cmd) createManagementToken(bootstrapACLToken types.BootStrapACLTokenInfo) (*ManagementACLTokenInfo, error) { if len(bootstrapACLToken.SecretID) == 0 { return nil, errors.New("bootstrap ACL token is required for creating management token") } diff --git a/internal/security/bootstrapper/command/setupacl/acltokens_test.go b/internal/security/bootstrapper/command/setupacl/acltokens_test.go index 1b28eae75b..676059f0de 100644 --- a/internal/security/bootstrapper/command/setupacl/acltokens_test.go +++ b/internal/security/bootstrapper/command/setupacl/acltokens_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" + "github.com/edgexfoundry/go-mod-secrets/v2/pkg/types" ) func TestIsACLTokenPersistent(t *testing.T) { @@ -81,9 +82,9 @@ func TestCreateAgentToken(t *testing.T) { ctx := context.Background() wg := &sync.WaitGroup{} lc := logger.MockLogger{} - testBootstrapToken := BootStrapACLTokenInfo{ + testBootstrapToken := types.BootStrapACLTokenInfo{ SecretID: "test-bootstrap-token", - Policies: []Policy{ + Policies: []types.Policy{ { ID: "00000000-0000-0000-0000-000000000001", Name: "global-management", @@ -93,7 +94,7 @@ func TestCreateAgentToken(t *testing.T) { tests := []struct { name string - bootstrapToken BootStrapACLTokenInfo + bootstrapToken types.BootStrapACLTokenInfo listTokensOkResponse bool listTokensRetriesOkResponse bool createTokenOkResponse bool @@ -104,7 +105,7 @@ func TestCreateAgentToken(t *testing.T) { {"Good:agent token ok response 2nd time or later", testBootstrapToken, true, true, true, true, false}, {"Bad:list tokens bad response", testBootstrapToken, false, false, true, false, true}, {"Bad:create token bad response", testBootstrapToken, true, true, false, false, true}, - {"Bad:empty bootstrap token", BootStrapACLTokenInfo{}, false, false, false, false, true}, + {"Bad:empty bootstrap token", types.BootStrapACLTokenInfo{}, false, false, false, false, true}, } for _, tt := range tests { @@ -157,9 +158,9 @@ func TestSetAgentTokenToAgent(t *testing.T) { ctx := context.Background() wg := &sync.WaitGroup{} lc := logger.MockLogger{} - testBootstrapToken := BootStrapACLTokenInfo{ + testBootstrapToken := types.BootStrapACLTokenInfo{ SecretID: "test-bootstrap-token", - Policies: []Policy{ + Policies: []types.Policy{ { ID: "00000000-0000-0000-0000-000000000001", Name: "global-management", @@ -170,14 +171,14 @@ func TestSetAgentTokenToAgent(t *testing.T) { tests := []struct { name string - bootstrapToken BootStrapACLTokenInfo + bootstrapToken types.BootStrapACLTokenInfo agentToken string setAgentTokenOkResponse bool expectedErr bool }{ {"Good:set agent token ok response", testBootstrapToken, testAgentToken, true, false}, {"Bad:set agent token bad response", testBootstrapToken, testAgentToken, false, true}, - {"Bad:empty bootstrap token", BootStrapACLTokenInfo{}, testAgentToken, false, true}, + {"Bad:empty bootstrap token", types.BootStrapACLTokenInfo{}, testAgentToken, false, true}, {"Bad:empty agent token", testBootstrapToken, "", false, true}, } diff --git a/internal/security/bootstrapper/command/setupacl/command.go b/internal/security/bootstrapper/command/setupacl/command.go index e04076a3cc..c8774f2b48 100644 --- a/internal/security/bootstrapper/command/setupacl/command.go +++ b/internal/security/bootstrapper/command/setupacl/command.go @@ -16,7 +16,6 @@ package setupacl import ( - "bytes" "context" "encoding/json" "errors" @@ -44,9 +43,10 @@ import ( "github.com/edgexfoundry/go-mod-secrets/v2/pkg" "github.com/edgexfoundry/go-mod-secrets/v2/pkg/token/authtokenloader" "github.com/edgexfoundry/go-mod-secrets/v2/pkg/token/fileioperformer" + "github.com/edgexfoundry/go-mod-secrets/v2/pkg/types" + "github.com/edgexfoundry/go-mod-secrets/v2/secrets" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" - "github.com/edgexfoundry/go-mod-core-contracts/v2/common" ) const ( @@ -71,7 +71,7 @@ type cmd struct { // internal state retryTimeout time.Duration - bootstrapACLTokenCache *BootStrapACLTokenInfo + bootstrapACLTokenCache *types.BootStrapACLTokenInfo secretstoreTokenCache string } @@ -142,8 +142,19 @@ func (c *cmd) Execute() (statusCode int, err error) { return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to retrieve secretstore token: %v", err) } - // configure Consul access with both Secret Store token and consul's bootstrap acl token - if err := c.configureConsulAccess(secretstoreToken, bootstrapACLToken.SecretID); err != nil { + clientConfig := types.SecretConfig{ + Type: secrets.Vault, + Host: c.configuration.SecretStore.Host, + Port: c.configuration.SecretStore.Port, + Protocol: c.configuration.SecretStore.Protocol, + } + client, err := secrets.NewSecretStoreClient(clientConfig, c.loggingClient, c.client) + 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) } @@ -240,7 +251,7 @@ func (c *cmd) reSetupEdgeXACLTokenRoles() error { return nil } -func (c *cmd) createBootstrapACLToken() (*BootStrapACLTokenInfo, error) { +func (c *cmd) createBootstrapACLToken() (*types.BootStrapACLTokenInfo, error) { bootstrapACLToken, err := c.generateBootStrapACLToken() if err != nil { // although we have a leader, but it is a very very rare chance that we could hit an error on legacy mode @@ -266,7 +277,7 @@ func (c *cmd) createBootstrapACLToken() (*BootStrapACLTokenInfo, error) { return bootstrapACLToken, nil } -func (c *cmd) saveACLTokens(bootstrapACLToken *BootStrapACLTokenInfo) error { +func (c *cmd) saveACLTokens(bootstrapACLToken *types.BootStrapACLTokenInfo) error { // Save the bootstrap ACL token into json file so that it can be used later on if err := c.saveBootstrapACLToken(bootstrapACLToken); err != nil { return fmt.Errorf("failed to save registry's bootstrap ACL token: %v", err) @@ -278,7 +289,7 @@ func (c *cmd) saveACLTokens(bootstrapACLToken *BootStrapACLTokenInfo) error { return nil } -func (c *cmd) createAndSaveManagementACLToken(bootstrapACLToken *BootStrapACLTokenInfo) error { +func (c *cmd) createAndSaveManagementACLToken(bootstrapACLToken *types.BootStrapACLTokenInfo) error { // create and save management token into json file in order to use later managementACLTokenInfo, err := c.createManagementToken(*bootstrapACLToken) if err != nil { @@ -329,13 +340,23 @@ func (c *cmd) createEdgeXACLTokenRoles(bootstrapACLTokenID, secretstoreToken str } // create roles based on the service keys as the role names - edgexACLTokenRole := NewRegistryRole(roleName, ClientType, []Policy{ + edgexACLTokenRole := types.NewRegistryRole(roleName, types.ClientType, []types.Policy{ *edgexServicePolicy, // localUse set to false as some EdgeX services may be running in a different node }, false) - // fail all if any one of the role creation failed - if err := c.createRole(secretstoreToken, edgexACLTokenRole); err != nil { + clientConfig := types.SecretConfig{ + Type: secrets.Vault, + Host: c.configuration.SecretStore.Host, + Port: c.configuration.SecretStore.Port, + Protocol: c.configuration.SecretStore.Protocol, + } + client, err := secrets.NewSecretStoreClient(clientConfig, c.loggingClient, c.client) + if err != nil { + return fmt.Errorf("failed to create SecretStoreClient: %s", err.Error()) + } + + if err := client.CreateRole(secretstoreToken, edgexACLTokenRole); err != nil { return fmt.Errorf("failed to create edgex role: %v", err) } } @@ -450,7 +471,7 @@ func getUniqueRolesFromEnv() (map[string]struct{}, error) { // setupAgentToken is to set up the agent token using the inputToken to the running agent if haven't set up yet // if the inputToken is nil then it will try to reconstruct from the saved file -func (c *cmd) setupAgentToken(inputToken *BootStrapACLTokenInfo) error { +func (c *cmd) setupAgentToken(inputToken *types.BootStrapACLTokenInfo) error { var err error setupAlreadyPrevious := false bootstrapACLToken := inputToken @@ -492,7 +513,7 @@ func (c *cmd) setupAgentToken(inputToken *BootStrapACLTokenInfo) error { } // reconstructBootstrapACLToken reads bootstrap ACL token from the saved file and reconstruct it into BootStrapACLTokenInfo -func (c *cmd) reconstructBootstrapACLToken() (*BootStrapACLTokenInfo, error) { +func (c *cmd) reconstructBootstrapACLToken() (*types.BootStrapACLTokenInfo, error) { if c.bootstrapACLTokenCache != nil { // re-use the cached one return c.bootstrapACLTokenCache, nil @@ -519,7 +540,7 @@ func (c *cmd) reconstructBootstrapACLToken() (*BootStrapACLTokenInfo, error) { return nil, fmt.Errorf("failed to open file reader: %v", err) } - var bootstrapACLToken BootStrapACLTokenInfo + var bootstrapACLToken types.BootStrapACLTokenInfo if err := json.NewDecoder(tokenReader).Decode(&bootstrapACLToken); err != nil { return nil, fmt.Errorf("failed to parse token data into BootStrapACLTokenInfo: %v", err) } @@ -649,65 +670,6 @@ func (c *cmd) retrieveSecretStoreTokenFromFile() (string, error) { return secretStoreToken, nil } -// configureConsulAccess is to enable the Consul config access to the SecretStore via consul/config/access API -// see the reference: https://www.vaultproject.io/api-docs/secret/consul#configure-access -func (c *cmd) configureConsulAccess(secretStoreToken string, bootstrapACLToken string) error { - configAccessURL := fmt.Sprintf("%s://%s:%d%s", c.configuration.SecretStore.Protocol, - c.configuration.SecretStore.Host, c.configuration.SecretStore.Port, consulConfigAccessVaultAPI) - _, err := url.Parse(configAccessURL) - if err != nil { - return fmt.Errorf("failed to parse config Access URL: %v", err) - } - - c.loggingClient.Debugf("configAccessURL: %s", configAccessURL) - - type ConfigAccess struct { - RegistryAddress string `json:"address"` - BootstrapACLToken string `json:"token"` - } - - payload := &ConfigAccess{ - RegistryAddress: fmt.Sprintf("%s:%d", c.configuration.StageGate.Registry.Host, c.configuration.StageGate.Registry.Port), - BootstrapACLToken: bootstrapACLToken, - } - - jsonPayload, err := json.Marshal(payload) - c.loggingClient.Tracef("payload: %v", payload) - if err != nil { - return fmt.Errorf("Failed to marshal JSON string payload: %v", err) - } - - req, err := http.NewRequest(http.MethodPost, configAccessURL, bytes.NewBuffer(jsonPayload)) - if err != nil { - return fmt.Errorf("Failed to prepare POST request for http URL: %w", err) - } - - req.Header.Add("X-Vault-Token", secretStoreToken) - req.Header.Add(common.ContentType, common.ContentTypeJSON) - resp, err := c.client.Do(req) - if err != nil { - return fmt.Errorf("Failed to send request for http URL: %w", err) - } - - defer func() { - _ = resp.Body.Close() - }() - - switch resp.StatusCode { - case http.StatusNoContent: - // no response body returned in this case - c.loggingClient.Info("successfully configure Consul access for secretstore") - return nil - default: - body, err := io.ReadAll(resp.Body) - if err != nil { - c.loggingClient.Errorf("cannot read resp.Body: %v", err) - } - return fmt.Errorf("failed to configure Consul access for secretstore via URL [%s] and status code= %d: %s", - configAccessURL, resp.StatusCode, string(body)) - } -} - func (c *cmd) writeSentinelFile() error { absPath, err := filepath.Abs(c.configuration.StageGate.Registry.ACL.SentinelFilePath) if err != nil { diff --git a/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go b/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go index 008dde483a..a91e7238cd 100644 --- a/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go +++ b/internal/security/bootstrapper/command/setupacl/stubregistryserver_test.go @@ -160,7 +160,7 @@ func (registry *registryTestServer) getRegistryServerConf(t *testing.T) *config. } else { w.WriteHeader(http.StatusForbidden) } - case fmt.Sprintf(createConsulRoleVaultAPI, pathBase): + case fmt.Sprintf("/v1/consul/roles/%s", pathBase): require.Equal(t, http.MethodPost, r.Method) if registry.serverOptions.createRoleOk { w.WriteHeader(http.StatusNoContent)