Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(security): Implement consul token header in API Gateway #3391

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ if [ "${ENABLE_REGISTRY_ACL}" == "true" ]; then
if [ "${setupACL_code}" -ne 0 ]; then
echo "$(date) failed to set up Consul ACL"
fi

# we need to grant the permission for proxy setup to read consul's token path so as to retrieve consul's token from it
echo "$(date) Changing ownership of consul token path to ${EDGEX_USER}:${EDGEX_GROUP}"
chown -Rh "${EDGEX_USER}":"${EDGEX_GROUP}" "${STAGEGATE_REGISTRY_ACL_BOOTSTRAPTOKENPATH}"
set -e
# no need to wait for Consul's port since it is in ready state after all ACL stuff
else
Expand Down
10 changes: 9 additions & 1 deletion cmd/security-proxy-setup/res/configuration.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#################################################################################
# Copyright 2019 Dell Inc.
# 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
Expand All @@ -20,6 +21,7 @@ LogLevel = "DEBUG"
SNIS = [""]
# RequestTimeout for proxy-setup http client caller
RequestTimeout = 10
AccessTokenFile = "/tmp/edgex/secrets/consul-acl-token/bootstrap_token.json"

[KongURL]
Server = "127.0.0.1"
Expand Down Expand Up @@ -93,4 +95,10 @@ RetryWaitPeriod = "1s"
Name = "virtualdevice"
Protocol = "http"
Host = "localhost"
Port = 49990
Port = 49990

[Routes.edgex-core-consul]
Name = "consul"
Protocol = "http"
Host = "localhost"
lenny-goodell marked this conversation as resolved.
Show resolved Hide resolved
Port = 8500
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func TestAddUserJWT(t *testing.T) {
"--public_key", publicKey,
"--jwt", jwt,
})

require.NoError(t, err)

// Execute command "addUser w/JWT"
Expand Down Expand Up @@ -185,6 +186,7 @@ func TestAddUserOAuth2(t *testing.T) {
"--redirect_uris", redirectUris,
"--jwt", jwt,
})

require.NoError(t, err)

// Execute command "addUser w/JWT"
Expand Down
3 changes: 2 additions & 1 deletion internal/security/config/command/proxy/oauth2/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"strings"

"github.com/edgexfoundry/edgex-go/internal"
"github.com/edgexfoundry/edgex-go/internal/security/config/command/proxy/common"
"github.com/edgexfoundry/edgex-go/internal/security/config/interfaces"
"github.com/edgexfoundry/edgex-go/internal/security/proxy/config"
"github.com/edgexfoundry/go-mod-secrets/v2/pkg"
Expand Down Expand Up @@ -101,7 +102,7 @@ func (c *cmd) Execute() (statusCode int, err error) {
}

// Add header values
req.Header.Add(clients.ContentType, "application/x-www-form-urlencoded")
req.Header.Add(clients.ContentType, common.UrlEncodedForm)
req.Header.Add(internal.AuthHeaderTitle, internal.BearerLabel+c.jwt)

// Execute the request
Expand Down
15 changes: 8 additions & 7 deletions internal/security/proxy/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ import (
)

type ConfigurationStruct struct {
LogLevel string
RequestTimeout int
SNIS []string
KongURL KongUrlInfo
KongAuth KongAuthInfo
SecretStore bootstrapConfig.SecretStoreInfo
Routes map[string]models.KongService
LogLevel string
RequestTimeout int
SNIS []string
AccessTokenFile string
KongURL KongUrlInfo
KongAuth KongAuthInfo
SecretStore bootstrapConfig.SecretStoreInfo
Routes map[string]models.KongService
}

type KongUrlInfo struct {
Expand Down
1 change: 1 addition & 0 deletions internal/security/proxy/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ const (
VaultToken = "X-Vault-Token"
OAuth2GrantType = "client_credentials"
OAuth2Scopes = "all"
URLEncodedForm = "application/x-www-form-urlencoded"
)
1 change: 0 additions & 1 deletion internal/security/proxy/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ func (r *Resource) Remove(path string) error {
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusNoContent:
r.loggingClient.Info(fmt.Sprintf("successful to delete %s at %s", r.name, path))
break
default:
e := fmt.Sprintf("failed to delete %s at %s with errocode %d.", r.name, path, resp.StatusCode)
r.loggingClient.Error(e)
Expand Down
34 changes: 21 additions & 13 deletions internal/security/proxy/service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*******************************************************************************
* Copyright 2019 Dell Inc.
* Copyright 2020 Intel Corp.
* Copyright 2021 Intel Corp.
*
* 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
Expand Down Expand Up @@ -46,6 +46,8 @@ const (

// AddProxyRoutesEnv is the environment variable name for adding the additional Kong routes for app services
AddProxyRoutesEnv = "ADD_PROXY_ROUTE"

edgeXCoreConsulServiceKey = "edgex-core-consul"
)

type CertError struct {
Expand Down Expand Up @@ -99,7 +101,6 @@ func (s *Service) checkServiceStatus(path string) error {
switch resp.StatusCode {
case http.StatusOK:
s.loggingClient.Info(fmt.Sprintf("the service on %s is up successfully", path))
break
default:
err = fmt.Errorf("unexpected http status %v %s", resp.StatusCode, path)
s.loggingClient.Error(err.Error())
Expand Down Expand Up @@ -145,13 +146,26 @@ func (s *Service) Init() error {

mergedRoutes := s.mergeRoutesWith(addRoutesFromEnv)

for _, route := range mergedRoutes {
for serviceKey, route := range mergedRoutes {

err := s.initKongService(&route)
if err != nil {
return err
}

// if it is edgex-core-consul service; then we need to enable the request transformer plugin
// in order to add the consul token header for that service
// see details on https://docs.konghq.com/hub/kong-inc/request-transformer/#enabling-the-plugin-on-a-service
if serviceKey == edgeXCoreConsulServiceKey {
s.loggingClient.Infof("try to enable service plugin for %s", edgeXCoreConsulServiceKey)
if err := s.addConsulTokenHeaderTo(&route); err != nil {
s.loggingClient.Errorf("failed to enable service plugin for %s: %v", edgeXCoreConsulServiceKey, err)
return err
}

s.loggingClient.Infof("service plugin for %s enabled", edgeXCoreConsulServiceKey)
}

routeParams := &models.KongRoute{
Paths: []string{"/" + strings.ToLower(route.Name)},
Name: strings.ToLower(route.Name),
Expand Down Expand Up @@ -295,7 +309,6 @@ func (s *Service) postCert(cp bootstrapConfig.CertKeyPair) *CertError {
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusConflict:
s.loggingClient.Info("successfully added certificate to the reverse proxy")
break
default:
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
Expand Down Expand Up @@ -328,7 +341,7 @@ func (s *Service) initKongService(service *models.KongService) error {
return fmt.Errorf("failed to construct http POST form request: %s %s", service.Name, err.Error())
}
req.Header.Add(internal.AuthHeaderTitle, internal.BearerLabel+s.bearerToken)
req.Header.Add(clients.ContentType, "application/x-www-form-urlencoded")
req.Header.Add(clients.ContentType, URLEncodedForm)

resp, err := s.client.Do(req)
if err != nil {
Expand All @@ -341,10 +354,8 @@ func (s *Service) initKongService(service *models.KongService) error {
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated:
s.loggingClient.Info(fmt.Sprintf("successful to set up proxy service for `%s` at `%s:%d`", service.Name, service.Host, service.Port))
break
case http.StatusConflict:
s.loggingClient.Info(fmt.Sprintf("proxy service for %s has been set up", service.Name))
break
default:
err = fmt.Errorf("proxy service for %s returned status %d", service.Name, resp.StatusCode)
s.loggingClient.Error(err.Error())
Expand All @@ -361,6 +372,7 @@ func (s *Service) initKongRoutes(r *models.KongRoute, name string) error {
}
tokens := []string{s.configuration.KongURL.GetProxyBaseURL(), ServicesPath, name, "routes"}

// Create routes associated to a specific service
req, err := http.NewRequest(http.MethodPost, strings.Join(tokens, "/"), strings.NewReader(string(data)))
if err != nil {
e := fmt.Sprintf("failed to set up routes for %s with error %s", name, err.Error())
Expand All @@ -382,7 +394,6 @@ func (s *Service) initKongRoutes(r *models.KongRoute, name string) error {
case http.StatusOK, http.StatusCreated, http.StatusConflict:
s.routes[name] = r
s.loggingClient.Info(fmt.Sprintf("successful to set up route for `%s` at `%v`", name, r.Paths))
break
default:
e := fmt.Sprintf("failed to set up route for %s with error %s", name, resp.Status)
s.loggingClient.Error(e)
Expand Down Expand Up @@ -414,7 +425,7 @@ func (s *Service) initJWTAuth() error {
s.loggingClient.Error(e)
return err
}
req.Header.Add(clients.ContentType, "application/x-www-form-urlencoded")
req.Header.Add(clients.ContentType, URLEncodedForm)

resp, err := s.client.Do(req)
if err != nil {
Expand All @@ -427,7 +438,6 @@ func (s *Service) initJWTAuth() error {
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusConflict:
s.loggingClient.Info("successful to set up jwt authentication")
break
default:
e := fmt.Sprintf("failed to set up jwt authentication with errorcode %d", resp.StatusCode)
s.loggingClient.Error(e)
Expand Down Expand Up @@ -461,7 +471,7 @@ func (s *Service) initOAuth2(ttl int) error {
s.loggingClient.Error(e)
return err
}
req.Header.Add(clients.ContentType, "application/x-www-form-urlencoded")
req.Header.Add(clients.ContentType, URLEncodedForm)

resp, err := s.client.Do(req)
if err != nil {
Expand All @@ -474,7 +484,6 @@ func (s *Service) initOAuth2(ttl int) error {
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusConflict:
s.loggingClient.Info("successful to set up oauth2 authentication")
break
default:
e := fmt.Sprintf("failed to set up oauth2 authentication with errorcode %d", resp.StatusCode)
s.loggingClient.Error(e)
Expand Down Expand Up @@ -506,7 +515,6 @@ func (s *Service) getSvcIDs(path string) (models.DataCollect, error) {
if err = json.NewDecoder(resp.Body).Decode(&collection); err != nil {
return collection, err
}
break
default:
e := fmt.Sprintf("failed to get list of %s with HTTP error code %d", path, resp.StatusCode)
s.loggingClient.Error(e)
Expand Down
Loading