Skip to content

Commit

Permalink
Merge pull request #178 from fisuda/hardening/support_kong
Browse files Browse the repository at this point in the history
Support kong
  • Loading branch information
fisuda committed Jul 17, 2021
2 parents 8755158 + c004071 commit 218f993
Show file tree
Hide file tree
Showing 10 changed files with 544 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NGSI Go v0.8.4-next

- Hardening: Support Kong (#178)
- Hardening: Add insecureSkipVerify option (#177)
- Fix: Fix URL path join (#176)
- Fix: Fix EOF error when ngsi-go-config.json is empty (#175)
Expand Down
16 changes: 16 additions & 0 deletions docs/management/broker.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ ngsi broker add \

#### Example 8

Orion with Kong (client credentials)

```
ngsi broker add \
--host kong \
--ngsiType v2 \
--brokerHost http://localhost:8000/ngsi \
--idmType kong \
--idmHost "https://localhost:8443/ngsi/oauth2/token,http://localhost:8001/" \
--clientId orion \
--clientSecret 1234
```

#### Example 9

Orion with Basic authentication

```console
Expand Down Expand Up @@ -263,6 +278,7 @@ Specify `v2` to `--ngsiType` when you add an alias for FIWARE Orion Context Brok
| [ThinkingCities](https://thinking-cities.readthedocs.io/) | idmHost, username, password | It provides auth token from Keystone |
| Keycloak | idmHost, username, password, clientId, clientSecret | It provides auth token from Keycloak |
| WSO2 | idmHost, username, password, clientId, clientSecret | It provides auth token from WSO2 |
| Kong (client credentials) | idmHost, clientId, clientSecret | It provides auth token from Kong |

### FIWARE Service and FIWARE ServicePath

Expand Down
1 change: 1 addition & 0 deletions docs/management/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ ngsi server add \
| [ThinkingCities](https://thinking-cities.readthedocs.io/) | idmHost, username, password | It provides auth token from Keystone |
| Keycloak | idmHost, username, password, clientId, clientSecret | It provides auth token from Keycloak |
| WSO2 | idmHost, username, password, clientId, clientSecret | It provides auth token from WSO2 |
| Kong (client credentials) | idmHost, clientId, clientSecret | It provides auth token from Kong |

### FIWARE Service and FIWARE ServicePath

Expand Down
7 changes: 6 additions & 1 deletion internal/ngsicmd/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,17 @@ func tokenCommand(c *cli.Context) error {
if err != nil {
return &ngsiCmdError{funcName, 8, err.Error(), err}
}
case ngsilib.CKong:
b, err = ngsilib.JSONMarshal(token.Kong)
if err != nil {
return &ngsiCmdError{funcName, 9, err.Error(), err}
}
}
if c.Bool("pretty") {
newBuf := new(bytes.Buffer)
err := ngsi.JSONConverter.Indent(newBuf, b, "", " ")
if err != nil {
return &ngsiCmdError{funcName, 9, err.Error(), err}
return &ngsiCmdError{funcName, 10, err.Error(), err}
}
fmt.Fprintln(ngsi.StdWriter, newBuf.String())
} else {
Expand Down
43 changes: 42 additions & 1 deletion internal/ngsicmd/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,47 @@ func TestTokenCommandErrorKeyrock(t *testing.T) {
}
}

func TestTokenCommandErrorKongJSON(t *testing.T) {
ngsi, set, app, _ := setupTest()

conf := `{
"version": "1",
"servers": {
"orion": {
"serverHost": "http://orion",
"ngsiType": "v2",
"idmType": "kong",
"idmHost": "http://kong-service,http://kong-idm",
"clientId": "11111111-2222-3333-4444-555555555555",
"clientSecret": "66666666-7777-8888-9999-000000000000"
}
}
}`
ngsi.FileReader = &MockFileLib{ReadFileData: []byte(conf)}

reqRes := MockHTTPReqRes{}
reqRes.Res.StatusCode = http.StatusOK
reqRes.ResBody = []byte(`{"expires_in":7200,"access_token":"G1y60yGbFE8OKXH6VHEOO1LGP0A5qyeO","token_type":"bearer"}`)
mock := NewMockHTTP()
mock.ReqRes = append(mock.ReqRes, reqRes)
ngsi.HTTP = mock
setupFlagString(set, "host")
set.Bool("verbose", false, "doc")

c := cli.NewContext(app, set, nil)
_ = set.Parse([]string{"--host=orion", "--verbose"})
setJSONEncodeErr(ngsi, 0)

err := tokenCommand(c)

if assert.Error(t, err) {
ngsiErr := err.(*ngsiCmdError)
assert.Equal(t, 9, ngsiErr.ErrNo)
assert.Equal(t, "json error", ngsiErr.Message)
assert.Error(t, err)
}
}

func TestTokenCommandErrorJSONPretty(t *testing.T) {
ngsi, set, app, _ := setupTest()

Expand Down Expand Up @@ -694,7 +735,7 @@ func TestTokenCommandErrorJSONPretty(t *testing.T) {

if assert.Error(t, err) {
ngsiErr := err.(*ngsiCmdError)
assert.Equal(t, 9, ngsiErr.ErrNo)
assert.Equal(t, 10, ngsiErr.ErrNo)
assert.Equal(t, "json error", ngsiErr.Message)
} else {
t.FailNow()
Expand Down
32 changes: 23 additions & 9 deletions internal/ngsilib/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const (
CBasic = "basic"
CKeycloak = "keycloak"
CWSO2 = "wso2"
CKong = "kong"
)

const (
Expand Down Expand Up @@ -120,10 +121,13 @@ var (
cContext, cFiwareService, cFiwareServicePath, cSafeString, cXAuthToken}
brokerTypeArgs = []string{cOrionLD, cScorpio, cStellio}
serverTypeArgs = []string{cComet, cCygnus, cQuantumLeap, cIota, cfiwareKeyrock, cPerseo, cPerseoCore, cWireCloud}
idmTypes = []string{CPasswordCredentials, CKeyrock, CKeyrocktokenprovider, CTokenproxy, CKeyrockIDM, CThinkingCities, CBasic, CKeycloak, CWSO2}
ngsiV2Types = []string{cNgsiV2, cNgsiv2, cV2}
ngsiLdTypes = []string{cNgsiLd, cLd}
apiPaths = []string{cPathRoot, cPathV2, cPathNgsiLd}
idmTypes = []string{
CPasswordCredentials, CKeyrock, CKeyrocktokenprovider, CTokenproxy, CKeyrockIDM,
CThinkingCities, CBasic, CKeycloak, CWSO2, CKong,
}
ngsiV2Types = []string{cNgsiV2, cNgsiv2, cV2}
ngsiLdTypes = []string{cNgsiLd, cLd}
apiPaths = []string{cPathRoot, cPathV2, cPathNgsiLd}
)

func (ngsi *NGSI) checkAllParams(host *Server) error {
Expand Down Expand Up @@ -243,20 +247,30 @@ func checkIdmParams(idmType string, idmHost string, username string, password st
return &LibError{funcName, 3, "required idmHost not found", nil}
}

if !(IsHTTP(idmHost) || strings.HasPrefix(idmHost, "/")) {
return &LibError{funcName, 4, fmt.Sprintf("idmHost error: %s", idmHost), nil}
if idmType == CKong {
hosts := strings.Split(idmHost, ",")
if !(len(hosts) == 2 && IsHTTP(hosts[0]) && IsHTTP(hosts[1])) {
return &LibError{funcName, 4, fmt.Sprintf("idmHost error: %s", idmHost), nil}
}
} else {
if !(IsHTTP(idmHost) || strings.HasPrefix(idmHost, "/")) {
return &LibError{funcName, 5, fmt.Sprintf("idmHost error: %s", idmHost), nil}
}
}
}

switch idmType {
case CKeyrock, CPasswordCredentials, CKeycloak, CWSO2:
case CKeyrock, CPasswordCredentials, CKeycloak, CWSO2, CKong:
if clientID == "" || clientSecret == "" {
return &LibError{funcName, 5, "clientID and clientSecret are needed", nil}
return &LibError{funcName, 6, "clientID and clientSecret are needed", nil}
}
if idmType == CKong {
return nil
}
fallthrough
case CKeyrocktokenprovider, CTokenproxy, CKeyrockIDM, CThinkingCities, CBasic:
if username == "" && password != "" {
return &LibError{funcName, 6, "username is needed", nil}
return &LibError{funcName, 7, "username is needed", nil}
}
}
return nil
Expand Down
22 changes: 19 additions & 3 deletions internal/ngsilib/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,12 @@ func TestCheckIdmParams(t *testing.T) {
assert.NoError(t, err)
}

func TestCheckIdmParamsKong(t *testing.T) {
err := checkIdmParams(CKong, "https://kong-service,http://kong-idm", "", "", "00000000-1111-2222-3333-444444444444", "55555555-6666-7777-8888-999999999999")

assert.NoError(t, err)
}

func TestCheckIdmParamsNoIdm(t *testing.T) {
err := checkIdmParams("", "", "", "", "", "")

Expand Down Expand Up @@ -398,12 +404,22 @@ func TestCheckIdmParamsErrorNoIdmHost(t *testing.T) {
}
}

func TestCheckIdmParamsErrorIdmHostKong(t *testing.T) {
err := checkIdmParams(CKong, "kong", "", "", "00000000-1111-2222-3333-444444444444", "55555555-6666-7777-8888-999999999999")

if assert.Error(t, err) {
ngsiErr := err.(*LibError)
assert.Equal(t, 4, ngsiErr.ErrNo)
assert.Equal(t, "idmHost error: kong", ngsiErr.Message)
}
}

func TestCheckIdmParamsErrorIdmHost(t *testing.T) {
err := checkIdmParams(CKeyrock, "fiware", "keyrock001@fiware", "0123456789", "00000000-1111-2222-3333-444444444444", "55555555-6666-7777-8888-999999999999")

if assert.Error(t, err) {
ngsiErr := err.(*LibError)
assert.Equal(t, 4, ngsiErr.ErrNo)
assert.Equal(t, 5, ngsiErr.ErrNo)
assert.Equal(t, "idmHost error: fiware", ngsiErr.Message)
}
}
Expand All @@ -413,7 +429,7 @@ func TestCheckIdmParamsErrorClientId(t *testing.T) {

if assert.Error(t, err) {
ngsiErr := err.(*LibError)
assert.Equal(t, 5, ngsiErr.ErrNo)
assert.Equal(t, 6, ngsiErr.ErrNo)
assert.Equal(t, "clientID and clientSecret are needed", ngsiErr.Message)
}
}
Expand All @@ -423,7 +439,7 @@ func TestCheckIdmParamsErrorUserPassword(t *testing.T) {

if assert.Error(t, err) {
ngsiErr := err.(*LibError)
assert.Equal(t, 6, ngsiErr.ErrNo)
assert.Equal(t, 7, ngsiErr.ErrNo)
assert.Equal(t, "username is needed", ngsiErr.Message)
}
}
Expand Down
139 changes: 139 additions & 0 deletions internal/ngsilib/token_kong.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
MIT License
Copyright (c) 2020-2021 Kazuhito Suda
This file is part of NGSI Go
https://github.com/lets-fiware/ngsi-go
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package ngsilib

import (
"fmt"
"net/http"
"net/url"
"path"
"strings"
"time"
)

// KongToken is ...
type KongToken struct {
ExpiresIn int64 `json:"expires_in"`
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
}

type idmKong struct {
}

const (
cKongService = 0
cKongIdm = 1
)

func (i *idmKong) requestToken(ngsi *NGSI, client *Client, tokenInfo *TokenInfo) (*TokenInfo, error) {
const funcName = "requestTokenKong"

var err error
var res *http.Response
var body []byte
var payloads []string
broker := client.Server

payloads = append(payloads, fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s", broker.ClientID, broker.ClientSecret))

for _, payload := range payloads {

headers := make(map[string]string)
u, _ := url.Parse(getKongHost(client.idmURL(), cKongService))
idm := Client{URL: u, Headers: headers, HTTP: ngsi.HTTP}

idm.SetHeader(cContentType, cAppXWwwFormUrlencoded)

res, body, err = idm.HTTPPost(payload)
if err != nil {
return nil, &LibError{funcName, 1, err.Error(), err}
}

switch res.StatusCode {
default:
gNGSI.Logging(LogInfo, fmt.Sprintf("%s %d\n", funcName, res.StatusCode))
continue
case http.StatusUnauthorized:
gNGSI.Logging(LogInfo, funcName+" Unauthorized\n")
continue
case http.StatusOK:
utime := ngsi.TimeLib.NowUnix()

var token KongToken
err = JSONUnmarshal(body, &token)
if err != nil {
return nil, &LibError{funcName, 2, err.Error(), err}
}

tokenInfo.Type = CKong
tokenInfo.Token = token.AccessToken
tokenInfo.Expires = time.Unix(utime+token.ExpiresIn, 0)
tokenInfo.RefreshToken = ""
tokenInfo.Kong = &token

return tokenInfo, nil
}
}

return nil, &LibError{funcName, 3, fmt.Sprintf("error %s %s", res.Status, string(body)), nil}
}

func (i *idmKong) revokeToken(ngsi *NGSI, client *Client, tokenInfo *TokenInfo) error {
const funcName = "revokeTokenKong"

headers := make(map[string]string)

u, _ := url.Parse(getKongHost(client.idmURL(), cKongIdm))
u.Path = path.Join(u.Path, "/oauth2_tokens/", tokenInfo.Token)
idm := Client{URL: u, Headers: headers, HTTP: ngsi.HTTP}

res, body, err := idm.HTTPDelete(nil)
if err != nil {
return &LibError{funcName, 1, err.Error(), err}
}
if res.StatusCode != http.StatusNoContent {
return &LibError{funcName, 2, fmt.Sprintf("error %s %s", res.Status, string(body)), nil}
}

return nil
}

func (i *idmKong) getAuthHeader(token string) (string, string) {
return "Authorization", "Bearer " + token
}

func getKongHost(idm string, i int) string {
hosts := strings.Split(idm, ",")
if len(hosts) <= i {
return "http://kong-error/"
}
return hosts[i]
}

0 comments on commit 218f993

Please sign in to comment.