Skip to content

Commit

Permalink
Merge pull request #101 from ARGOeu/devel
Browse files Browse the repository at this point in the history
Merge Devel into master to release version 0.1.3
  • Loading branch information
kkoumantaros authored Jun 13, 2019
2 parents ccd9ef4 + 47fad41 commit 611afb5
Show file tree
Hide file tree
Showing 34 changed files with 1,014 additions and 305 deletions.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,19 @@ Before you start, you need to issue a valid certificate.
"certificate_key":"/path/to/key/localhost.key",
"service_token": "some-token",
"supported_auth_types": ["x509"],
"supported_auth_methods": ["api-key"],
"supported_service_types": ["ams"],
"supported_auth_methods": ["api-key", "headers"],
"supported_service_types": ["ams", "web-api"],
"verify_ssl": true,
"trust_unknown_cas": false,
"verify_certificate": true,
"service_types_paths": {"ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}"},
"service_types_retrieval_fields": {"ams": "token"}
"service_types_paths": {
"ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}",
"web-api": "/api/v2/users:byID/{{identifier}}?export=flat"
},
"service_types_retrieval_fields": {
"ams": "token",
"web-api": "api_key"
}
}
```

Expand Down Expand Up @@ -116,6 +122,8 @@ but a reverse dns look up returns another hostname for the client from where the

## Feature Milestones

- Add support for authenticating with external services through x-api-key header.
- Add default configuration for interacting easier with the [argo-web-api](https://github.com/ARGOeu/argo-web-api).
- ~~Add support for authenticating with external services through x-api-key header.~~

- ~~Add default configuration for interacting easier with the [argo-web-api](https://github.com/ARGOeu/argo-web-api).~~

- Add support for using OIDC tokens as an alternative authentication mechanism.
8 changes: 7 additions & 1 deletion argo-api-authn.spec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Name: argo-api-authn
Summary: ARGO Authentication API. Map X509, OICD to token.
Version: 0.1.2
Version: 0.1.3
Release: 1%{?dist}
License: ASL 2.0
Buildroot: %{_tmppath}/%{name}-buildroot
Expand Down Expand Up @@ -57,6 +57,12 @@ go clean
%attr(0644,root,root) /usr/lib/systemd/system/argo-api-authn.service

%changelog
* Thu Jun 13 2019 Agelos Tsalapatis <[email protected]> - 0.1.3-1%{?dist}
- ARGO-1773 Update authn scripts to filter service endpoints before creating the respective user
- ARGO-1615 update authn scripts to get site-mail from gocdb
- ARGO-1738 Add support for interacting with the argo-web-api
- ARGO-1737 Add support for headers auth method
- ARGO-1740 Change binding structure to be more generic
* Thu Mar 7 2019 Agelos Tsalapatis <[email protected]> - 0.1.2-1%{?dist}
- ARGO-1659 Authn should not start if there is no database connection established
- Utility script that creates users and topics per site
Expand Down
4 changes: 3 additions & 1 deletion auth/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ SoPmZKiBeb+2OQ2n7+FI8ftkqxWw6zjh651brAoy/0zqLTRPh+c=
err1 := CertHasExpired(crt)

// expired case
crt.NotAfter = time.Now().AddDate(0,0,-1)
crt.NotAfter = time.Now().AddDate(0, 0, -1)
err2 := CertHasExpired(crt)

// not active yet
crt = ParseCert(commonCert)
// move the not before date a day to the future so the check fails because we haven't reached that date yet
crt.NotBefore = time.Now().Add(time.Hour * 24)
// also move the not after date so we can skip the expiration case and check the not before case
crt.NotAfter = time.Now().Add(time.Hour * 24)
err3 := CertHasExpired(crt)

suite.Nil(err1)
Expand Down
2 changes: 1 addition & 1 deletion authmethods/api_key_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,6 @@ func (suite *ApiKeyAuthMethodTestSuite) TestUpdate() {

}

func TestApiKeyAuthMethod_Fill(t *testing.T) {
func TestApiKeyAuthMethodSuite(t *testing.T) {
suite.Run(t, new(ApiKeyAuthMethodTestSuite))
}
2 changes: 2 additions & 0 deletions authmethods/authmethods.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ type AuthMethodInit func() AuthMethod

var AuthMethodsTypes = map[string]AuthMethodInit{
"api-key": NewApiKeyAuthMethod,
"headers": NewHeadersAuthMethod,
}

// A function type that refers to all the query functions for all the respective tuh method types
type QueryAuthMethodFinder func(serviceUUID string, host string, store stores.Store) ([]stores.QAuthMethod, error)

var QueryAuthMethodFinders = map[string]QueryAuthMethodFinder{
"api-key": ApiKeyAuthFinder,
"headers": HeadersAuthFinder,
}

type AuthMethod interface {
Expand Down
9 changes: 7 additions & 2 deletions authmethods/authmethods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodFIndAll() {
amb1 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""}
am1 := &ApiKeyAuthMethod{AccessKey: "access_key"}
am1.BasicAuthMethod = amb1
expAmList.AuthMethods = append(expAmList.AuthMethods, am1)

amb2 := BasicAuthMethod{ServiceUUID: "uuid2", Host: "host3", Port: 9000, Type: "headers", UUID: "am_uuid_2", CreatedOn: ""}
am2 := &HeadersAuthMethod{Headers: map[string]string{"x-api-key": "key-1", "Accept": "application/json"}}
am2.BasicAuthMethod = amb2

expAmList.AuthMethods = append(expAmList.AuthMethods, am1, am2)

aMList, err1 := AuthMethodFindAll(mockstore)

Expand All @@ -162,7 +167,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodDelete() {

err1 := AuthMethodDelete(am1, mockstore)

suite.Equal(0, len(mockstore.AuthMethods))
suite.Equal(1, len(mockstore.AuthMethods))

suite.Nil(err1)
}
Expand Down
208 changes: 208 additions & 0 deletions authmethods/headers_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package authmethods

import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/ARGOeu/argo-api-authn/bindings"
"github.com/ARGOeu/argo-api-authn/config"
"github.com/ARGOeu/argo-api-authn/servicetypes"
"github.com/ARGOeu/argo-api-authn/stores"
"github.com/ARGOeu/argo-api-authn/utils"
LOGGER "github.com/sirupsen/logrus"
"io"
"net/http"
"strconv"
"strings"
"time"
)

type HeadersAuthMethod struct {
BasicAuthMethod
Headers map[string]string `json:"headers" required:"true"`
}

// TempHeadersAuthMethod represents the fields that are allowed to be modified
type TempHeadersAuthMethod struct {
TempBasicAuthMethod
Headers map[string]string `json:"headers" required:"true"`
}

func NewHeadersAuthMethod() AuthMethod {
return new(HeadersAuthMethod)
}

func (m *HeadersAuthMethod) Validate(store stores.Store) error {

var err error

// check if the embedded struct is valid
if err = m.BasicAuthMethod.Validate(store); err != nil {
return err
}

// check if all required field have been provided
if err = utils.ValidateRequired(*m); err != nil {
err := utils.APIErrEmptyRequiredField("auth method", err.Error())
return err
}

// check that the headers map is not empty
if len(m.Headers) == 0 {
err := utils.APIErrEmptyRequiredField("auth method", utils.GenericEmptyRequiredField("headers").Error())
return err
}

return err
}

func (m *HeadersAuthMethod) Update(r io.ReadCloser) (AuthMethod, error) {

var err error
var authMBytes []byte
var tempAM TempHeadersAuthMethod

var updatedAM = NewHeadersAuthMethod()

// first fill the temp auth method with the already existing data
// convert the existing auth method to bytes
if authMBytes, err = json.Marshal(*m); err != nil {
err := utils.APIGenericInternalError(err.Error())
return updatedAM, err
}

// then load the bytes into the temp auth method
if err = json.Unmarshal(authMBytes, &tempAM); err != nil {
err := utils.APIGenericInternalError(err.Error())
return updatedAM, err
}

// check the validity of the JSON and fill the temp auth method object with the updated data
if err = json.NewDecoder(r).Decode(&tempAM); err != nil {
err := utils.APIErrBadRequest(err.Error())
return updatedAM, err
}

// close the reader
if err = r.Close(); err != nil {
err := utils.APIGenericInternalError(err.Error())
return updatedAM, err
}

// fill the updated auth method with the already existing data
if err := utils.CopyFields(*m, updatedAM); err != nil {
err = utils.APIGenericInternalError(err.Error())
return updatedAM, err
}

// transfer the updated temporary data to the updated auth method object
// in order to override the outdated fields
// convert to bytes
if authMBytes, err = json.Marshal(tempAM); err != nil {
err := utils.APIGenericInternalError(err.Error())
return updatedAM, err
}

// then load the bytes
if err = json.Unmarshal(authMBytes, updatedAM); err != nil {
err := utils.APIGenericInternalError(err.Error())
return updatedAM, err
}

return updatedAM, err
}

func (m *HeadersAuthMethod) RetrieveAuthResource(binding bindings.Binding, serviceType servicetypes.ServiceType, cfg *config.Config) (map[string]interface{}, error) {

var externalResp map[string]interface{}
var err error
var ok bool
var resp *http.Response
var authResource interface{}
var retrievalField string
var path string

if retrievalField, ok = cfg.ServiceTypesRetrievalFields[serviceType.Type]; !ok {
err = utils.APIGenericInternalError("Backend error")
LOGGER.Errorf("The retrieval field for type: %v was not found in the config retrieval fields: %v", serviceType.Type, cfg.ServiceTypesRetrievalFields)
return externalResp, err
}

if path, ok = cfg.ServiceTypesPaths[serviceType.Type]; !ok {
err = utils.APIGenericInternalError("Backend error")
LOGGER.Errorf("The path for type: %v was not found in the config retrieval fields: %v", serviceType.Type, cfg.ServiceTypesPaths)
return externalResp, err
}

// build the path that identifies the resource we are going to request
resourcePath := fmt.Sprintf("https://%v:%v%v", m.Host, strconv.Itoa(m.Port), path)
resourcePath = strings.Replace(resourcePath, "{{identifier}}", binding.UniqueKey, 1)

// build the client and execute the request
transCfg := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !cfg.VerifySSL},
}

client := &http.Client{Transport: transCfg, Timeout: time.Duration(30 * time.Second)}

req, err := http.NewRequest(http.MethodGet, resourcePath, nil)
if err != nil {
err = utils.APIGenericInternalError(err.Error())
}

// populate the request with the headers
for k, v := range m.Headers {
req.Header.Add(k, v)
}

resp, err = client.Do(req)
if err != nil {
err = utils.APIGenericInternalError(err.Error())
return externalResp, err
}

// evaluate the response
if resp.StatusCode >= 400 {
// convert the entire response body into a string and include into a genericAPIError
buf := bytes.Buffer{}
buf.ReadFrom(resp.Body)
err = utils.APIGenericInternalError(buf.String())
return externalResp, err
}

// get the response from the service type
if err = json.NewDecoder(resp.Body).Decode(&externalResp); err != nil {
err = utils.APIGenericInternalError(err.Error())
return externalResp, err
}

defer resp.Body.Close()

// check if the retrieval field that we need is present in the response
if authResource, ok = externalResp[retrievalField]; !ok {
err = utils.APIGenericInternalError(fmt.Sprintf("The specified retrieval field: `%v` was not found in the response body of the service type", retrievalField))
return externalResp, err
}

// if everything went ok, return the appropriate response field
return map[string]interface{}{"token": authResource}, err

}

func HeadersAuthFinder(serviceUUID string, host string, store stores.Store) ([]stores.QAuthMethod, error) {

var err error
var qAms []stores.QAuthMethod
var qApiAms []stores.QHeadersAuthMethod

if qApiAms, err = store.QueryHeadersAuthMethods(serviceUUID, host); err != nil {
return qAms, err
}

for _, apim := range qApiAms {
qAms = append(qAms, &apim)
}

return qAms, err
}
Loading

0 comments on commit 611afb5

Please sign in to comment.