Skip to content

Commit

Permalink
feat(security): Add consul token header to Kong's service
Browse files Browse the repository at this point in the history
- Add implementation for enabling request transformer plugin on consul service for Kong's route through proxy-setup
- Fix unit test issues
- Add necessary starting sequence and env. for proxy-setup in snap packaging

Closes: #3368

Signed-off-by: Jim Wang <[email protected]>
  • Loading branch information
jim-wang-intel committed Apr 21, 2021
1 parent 5046ead commit 3daad41
Show file tree
Hide file tree
Showing 16 changed files with 534 additions and 40 deletions.
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"
Port = 8500
4 changes: 2 additions & 2 deletions internal/security/config/command/proxy/adduser/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (c *cmd) ExecuteAddJwt() (int, error) {
return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to prepare request to associate JWT to user %s: %w", c.username, err)
}
req.Header.Add(clients.ContentType, common.UrlEncodedForm)
req.Header.Add("Authorization", "Bearer "+jwt)
req.Header.Add(interfaces.Authorization, interfaces.Bearer+jwt)
resp, err := c.client.Do(req)
if err != nil {
return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to send request to associate JWT to user %s: %w", c.username, err)
Expand Down Expand Up @@ -233,7 +233,7 @@ func (c *cmd) ExecuteAddOAuth2() (statusCode int, err error) {
return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to prepare request to create oauth application %s: %w", c.username, err)
}
req.Header.Add(clients.ContentType, common.UrlEncodedForm)
req.Header.Add("Authorization", "Bearer "+jwt)
req.Header.Add(interfaces.Authorization, interfaces.Bearer+jwt)
resp, err := c.client.Do(req)
if err != nil {
return interfaces.StatusCodeExitWithError, fmt.Errorf("failed to send request to create oauth application %s: %w", c.username, err)
Expand Down
31 changes: 25 additions & 6 deletions internal/security/config/command/proxy/adduser/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ package adduser
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strconv"
"testing"

Expand Down Expand Up @@ -68,8 +70,8 @@ func TestAddUserJWT(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// Check to make sure the JWT authorization header exists
assert.NotNil(t, r.Header.Values("Authorization"))
require.Equal(t, "Bearer "+jwt, r.Header.Values("Authorization")[0])
assert.NotNil(t, r.Header.Values(interfaces.Authorization))
require.Equal(t, interfaces.Bearer+jwt, r.Header.Values(interfaces.Authorization)[0])

switch r.URL.EscapedPath() {

Expand Down Expand Up @@ -104,14 +106,19 @@ func TestAddUserJWT(t *testing.T) {
config.KongURL.Server = tsURL.Hostname()
config.KongURL.ApplicationPort, _ = strconv.Atoi(tsURL.Port())

jwtFile := prepareJWTFile(t, jwt)

// Setup command "addUser w/JWT"
command, err := NewCommand(lc, config, []string{
"--token-type", tokenType,
"--user", user,
"--algorithm", algorithm,
"--public_key", publicKey,
"--jwt", jwt,
"--jwt", jwtFile,
})

defer func() { _ = os.Remove(jwtFile) }()

require.NoError(t, err)

// Execute command "addUser w/JWT"
Expand Down Expand Up @@ -139,8 +146,8 @@ func TestAddUserOAuth2(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// Check to make sure the JWT authorization header exists
assert.NotNil(t, r.Header.Values("Authorization"))
require.Equal(t, "Bearer "+jwt, r.Header.Values("Authorization")[0])
assert.NotNil(t, r.Header.Values(interfaces.Authorization))
require.Equal(t, interfaces.Bearer+jwt, r.Header.Values(interfaces.Authorization)[0])

switch r.URL.EscapedPath() {

Expand Down Expand Up @@ -177,13 +184,18 @@ func TestAddUserOAuth2(t *testing.T) {
config.KongURL.Server = tsURL.Hostname()
config.KongURL.ApplicationPort, _ = strconv.Atoi(tsURL.Port())

jwtFile := prepareJWTFile(t, jwt)

// Setup command "addUser w/OAuth2"
command, err := NewCommand(lc, config, []string{
"--token-type", tokenType,
"--user", user,
"--redirect_uris", redirectUris,
"--jwt", jwt,
"--jwt", jwtFile,
})

defer func() { _ = os.Remove(jwtFile) }()

require.NoError(t, err)

// Execute command "addUser w/JWT"
Expand All @@ -193,3 +205,10 @@ func TestAddUserOAuth2(t *testing.T) {
require.NoError(t, err)
require.Equal(t, interfaces.StatusCodeExitNormal, code)
}

func prepareJWTFile(t *testing.T, jwtData string) string {
jwtFile := "testJwtFile"
err := ioutil.WriteFile(jwtFile, []byte(jwtData), 0600)
require.NoError(t, err)
return jwtFile
}
2 changes: 1 addition & 1 deletion internal/security/config/command/proxy/deluser/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (c *cmd) Execute() (int, error) {
if err != nil {
return interfaces.StatusCodeExitWithError, fmt.Errorf("Failed to prepare delete consumer request %s: %w", c.username, err)
}
req.Header.Add("Authorization", "Bearer "+c.jwt)
req.Header.Add(interfaces.Authorization, interfaces.Bearer+c.jwt)

resp, err := c.client.Do(req)
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions 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,8 +102,8 @@ func (c *cmd) Execute() (statusCode int, err error) {
}

// Add header values
req.Header.Add(clients.ContentType, "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+c.adminJWT)
req.Header.Add(clients.ContentType, common.UrlEncodedForm)
req.Header.Add(interfaces.Authorization, interfaces.Bearer+c.adminJWT)

// Execute the request
resp, err := c.client.Do(req)
Expand Down
6 changes: 3 additions & 3 deletions internal/security/config/command/proxy/tls/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (c *cmd) listKongTLSCertificates() (certificateIDs, error) {
if err != nil {
return nil, fmt.Errorf("failed to prepare request to list Kong snis tls certs: %w", err)
}
req.Header.Add("Authorization", "Bearer "+c.adminApiJwt)
req.Header.Add(interfaces.Authorization, interfaces.Bearer+c.adminApiJwt)

resp, err := c.client.Do(req)
if err != nil {
Expand Down Expand Up @@ -217,7 +217,7 @@ func (c *cmd) deleteKongTLSCertificateById(certId string) error {
if err != nil {
return fmt.Errorf("failed to prepare request to delete Kong tls cert: %w", err)
}
req.Header.Add("Authorization", "Bearer "+c.adminApiJwt)
req.Header.Add(interfaces.Authorization, interfaces.Bearer+c.adminApiJwt)

resp, err := c.client.Do(req)
if err != nil {
Expand Down Expand Up @@ -255,7 +255,7 @@ func (c *cmd) postKongTLSCertificate(certKeyPair *bootstrapConfig.CertKeyPair) e
}

req.Header.Add(clients.ContentType, common.UrlEncodedForm)
req.Header.Add("Authorization", "Bearer "+c.adminApiJwt)
req.Header.Add(interfaces.Authorization, interfaces.Bearer+c.adminApiJwt)

resp, err := c.client.Do(req)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions internal/security/config/interfaces/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ const (
RS256 string = "RS256"
// ES256 JWT Alt ES256
ES256 string = "ES256"
// Authorization is the standard authorization header for http authentication
Authorization = "Authorization"
// Bearer is the standard spec. for using token authentication
Bearer = "Bearer "
)
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
3 changes: 3 additions & 0 deletions internal/security/proxy/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ const (
VaultToken = "X-Vault-Token"
OAuth2GrantType = "client_credentials"
OAuth2Scopes = "all"
Authorization = "Authorization"
Bearer = "Bearer "
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
39 changes: 24 additions & 15 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,27 @@ 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.enableRequestTransformerPlugin(&route)
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 +310,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 @@ -327,8 +341,8 @@ func (s *Service) initKongService(service *models.KongService) error {
if err != nil {
return fmt.Errorf("failed to construct http POST form request: %s %s", service.Name, err.Error())
}
req.Header.Add("Authorization", "Bearer "+s.bearerToken)
req.Header.Add(clients.ContentType, "application/x-www-form-urlencoded")
req.Header.Add(Authorization, Bearer+s.bearerToken)
req.Header.Add(clients.ContentType, URLEncodedForm)

resp, err := s.client.Do(req)
if err != nil {
Expand All @@ -341,10 +355,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,13 +373,14 @@ 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())
s.loggingClient.Error(e)
return err
}
req.Header.Add("Authorization", "Bearer "+s.bearerToken)
req.Header.Add(Authorization, Bearer+s.bearerToken)
req.Header.Add(clients.ContentType, clients.ContentTypeJSON)

resp, err := s.client.Do(req)
Expand All @@ -382,7 +395,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 +426,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 +439,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 +472,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 +485,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 +516,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

0 comments on commit 3daad41

Please sign in to comment.