Skip to content

Commit 78572ff

Browse files
authored
Merge pull request #135 from ARGOeu/devel
Version 0.1.6
2 parents e9e8934 + ed2289d commit 78572ff

File tree

16 files changed

+557
-155
lines changed

16 files changed

+557
-155
lines changed

Jenkinsfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ pipeline {
6464
success {
6565
script{
6666
if ( env.BRANCH_NAME == 'devel' ) {
67-
build job: '/ARGO-utils/argo-swagger-docs', propagate: false
6867
build job: '/ARGO/argodoc/devel', propagate: false
6968
} else if ( env.BRANCH_NAME == 'master' ) {
7069
build job: '/ARGO/argodoc/master', propagate: false

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,16 @@ Before you start, you need to issue a valid certificate.
8282
"ams": "token",
8383
"web-api": "api_key"
8484
},
85-
"syslog_enabled": true
85+
"syslog_enabled": true,
86+
"client_cert_host_verification": true
8687
}
8788
```
8889

8990
## Important Notes
9091
It is important to notice that since we need to verify the provided certificate’s hostname,
9192
the client has to make sure that both Forward and Reverse DNS lookup on the client is correctly setup
92-
and that the hostname corresponds to the certificate used. For both IPv4 and IPv6 (if used)
93+
and that the hostname corresponds to the certificate used. For both IPv4 and IPv6 (if used).
94+
This functionality is controlled by the configuration ` client_cert_host_verification` value.
9395

9496
### Common errors
9597
- Executing a request using IPv6 without having a properly configured reverse DNS.

argo-api-authn.service

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
[Unit]
22
Description=ARGO Authentication Service
3-
Requires=mongod.service
43

54
[Service]
65
SyslogIdentifier=argo_api_authn

argo-api-authn.spec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
Name: argo-api-authn
55
Summary: ARGO Authentication API. Map X509, OICD to token.
6-
Version: 0.1.5
6+
Version: 0.1.6
77
Release: 1%{?dist}
88
License: ASL 2.0
99
Buildroot: %{_tmppath}/%{name}-buildroot
@@ -57,6 +57,8 @@ go clean
5757
%attr(0644,root,root) /usr/lib/systemd/system/argo-api-authn.service
5858

5959
%changelog
60+
* Wed Mar 31 2021 Agelos Tsalapatis <agelos.tsal@gmail .com> - 0.1.6-1%{?dist}
61+
- Release of argo-api-authn version 0.1.6
6062
* Wed Nov 18 2020 Agelos Tsalapatis <agelos.tsal@gmail .com> - 0.1.5-1%{?dist}
6163
- Release of argo-api-authn version 0.1.5
6264
* Thu Jun 13 2019 Agelos Tsalapatis <agelos.tsal@gmail.com> - 0.1.4-1%{?dist}

auth/certificate.go

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@ import (
1414
"time"
1515
)
1616

17-
var ExtraAttributeNames = map[string]string{
18-
"0.9.2342.19200300.100.1.25": "DC",
17+
const (
18+
DomainComponentRDN = "DC"
19+
EmailAddressRDN = "E"
20+
)
21+
22+
var NonStandardAttributeNames = map[string]string{
23+
"0.9.2342.19200300.100.1.25": DomainComponentRDN,
24+
"1.2.840.113549.1.9.1": EmailAddressRDN,
1925
}
2026

2127
// load_CAs reads the root certificates from a directory within the filesystem, and creates the trusted root CA chain
@@ -55,26 +61,35 @@ func ExtractEnhancedRDNSequenceToString(cert *x509.Certificate) string {
5561
var sb strings.Builder
5662

5763
// create a map that will hold the values of the additional RDNs that we have defined
58-
// make sure that the initialized keys match the values of the ExtraAttributeNames map defined in this package
64+
// make sure that the initialized keys match the values of the NonStandardAttributeNames map defined in this package
5965
extraRDNS := map[string][]string{}
60-
extraRDNS["DC"] = []string{}
66+
extraRDNS[DomainComponentRDN] = []string{}
67+
extraRDNS[EmailAddressRDN] = []string{}
6168

6269
// loop through the attribute names of the cert
6370
// if the type matches any of the predefined asn1.ObjectIdentifiers then append its value to the respective rdn
6471
for i := 0; i < len(cert.Subject.Names); i++ {
6572
atv := cert.Subject.Names[i]
66-
if value, ok := ExtraAttributeNames[atv.Type.String()]; ok {
73+
if value, ok := NonStandardAttributeNames[atv.Type.String()]; ok {
6774
extraRDNS[value] = append(extraRDNS[value], atv.Value.(string))
6875
}
6976

7077
}
7178

79+
// check if the Email RDN was present
80+
// EMAIL RDN is more specific than CN so it should be at start of the DN string
81+
if len(extraRDNS[EmailAddressRDN]) > 0 {
82+
sb.WriteString(FormatRdnToString(EmailAddressRDN, extraRDNS[EmailAddressRDN]))
83+
sb.WriteString(",")
84+
}
85+
7286
sb.WriteString(cert.Subject.ToRDNSequence().String())
7387

7488
// check the extra RDNs if the have any registered values
75-
if len(extraRDNS["DC"]) > 0 {
89+
// DC RDN is the most generic one so it belongs at the end of the DN string
90+
if len(extraRDNS[DomainComponentRDN]) > 0 {
7691
sb.WriteString(",")
77-
sb.WriteString(FormatRdnToString("DC", extraRDNS["DC"]))
92+
sb.WriteString(FormatRdnToString("DC", extraRDNS[DomainComponentRDN]))
7893
}
7994

8095
return sb.String()
@@ -108,40 +123,43 @@ func FormatRdnToString(rdn string, rdnValues []string) string {
108123
}
109124

110125
// ValidateClientCertificate performs a number of different checks to ensure the provided certificate is valid
111-
func ValidateClientCertificate(cert *x509.Certificate, clientIP string) error {
126+
func ValidateClientCertificate(cert *x509.Certificate, clientIP string, clientCertHostVerification bool) error {
112127

113128
var err error
114129
var hosts []string
115130
var ip string
116131

117-
if ip, _, err = net.SplitHostPort(clientIP); err != nil {
118-
err := &utils.APIError{Code: 403, Message: err.Error(), Status: "ACCESS_FORBIDDEN"}
119-
return err
120-
}
132+
if clientCertHostVerification {
121133

122-
if hosts, err = net.LookupAddr(ip); err != nil {
123-
err = &utils.APIError{Message: err.Error(), Code: 400, Status: "BAD REQUEST"}
124-
return err
125-
}
134+
if ip, _, err = net.SplitHostPort(clientIP); err != nil {
135+
err := &utils.APIError{Code: 403, Message: err.Error(), Status: "ACCESS_FORBIDDEN"}
136+
return err
137+
}
126138

127-
LOGGER.Infof("Certificate request: %v from Host: %v with IP: %v", ExtractEnhancedRDNSequenceToString(cert), hosts, clientIP)
128-
129-
// loop through hosts and check if any of them matches with the one specified in the certificate
130-
var tmpErr error
131-
for _, h := range hosts {
132-
// if there is an error, hold a temporary error and move to next host
133-
if err = cert.VerifyHostname(h); err != nil {
134-
tmpErr = &utils.APIError{Code: 403, Message: err.Error(), Status: "ACCESS_FORBIDDEN"}
135-
// if there is no error, clear the temporary error and break out of the check loop,
136-
// if we don't break the loop, if there is another host declared, it will declare a temporary error
137-
} else {
138-
tmpErr = nil
139-
break
139+
if hosts, err = net.LookupAddr(ip); err != nil {
140+
err = &utils.APIError{Message: err.Error(), Code: 400, Status: "BAD REQUEST"}
141+
return err
140142
}
141-
}
142143

143-
if tmpErr != nil {
144-
return tmpErr
144+
LOGGER.Infof("Certificate request: %v from Host: %v with IP: %v", ExtractEnhancedRDNSequenceToString(cert), hosts, clientIP)
145+
146+
// loop through hosts and check if any of them matches with the one specified in the certificate
147+
var tmpErr error
148+
for _, h := range hosts {
149+
// if there is an error, hold a temporary error and move to next host
150+
if err = cert.VerifyHostname(h); err != nil {
151+
tmpErr = &utils.APIError{Code: 403, Message: err.Error(), Status: "ACCESS_FORBIDDEN"}
152+
// if there is no error, clear the temporary error and break out of the check loop,
153+
// if we don't break the loop, if there is another host declared, it will declare a temporary error
154+
} else {
155+
tmpErr = nil
156+
break
157+
}
158+
}
159+
160+
if tmpErr != nil {
161+
return tmpErr
162+
}
145163
}
146164

147165
// check if the certificate has expired

auth/certificate_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,21 @@ SoPmZKiBeb+2OQ2n7+FI8ftkqxWw6zjh651brAoy/0zqLTRPh+c=
5151
enhancedCert.Subject.Names = append(enhancedCert.Subject.Names, extraAttributeValue1, extraAttributeValue2)
5252
ers2 := ExtractEnhancedRDNSequenceToString(enhancedCert)
5353

54+
// cert with all possible supported rdns
55+
enhancedCert2 := ParseCert(commonCert)
56+
enhancedCert2.Subject.CommonName = "service/example.com"
57+
enhancedCert2.Subject.StreetAddress = []string{"7 Street Ave"}
58+
enhancedCert2.Subject.OrganizationalUnit = []string{"organizational unit 2"}
59+
enhancedCert2.Subject.PostalCode = []string{"17121"}
60+
emailObj := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
61+
extraAttributeValuemail := pkix.AttributeTypeAndValue{Type: emailObj, Value: "email@example.com"}
62+
enhancedCert2.Subject.Names = append(enhancedCert2.Subject.Names, extraAttributeValue1, extraAttributeValue2, extraAttributeValuemail)
63+
ers3 := ExtractEnhancedRDNSequenceToString(enhancedCert2)
64+
5465
suite.Equal("O=COMPANY,L=CITY,ST=TN,C=TC", ers)
5566
suite.Equal("O=COMPANY,L=CITY,ST=TN,C=TC,DC=v1+DC=v2", ers2)
67+
suite.Equal("E=email@example.com,CN=service/example.com,OU=organizational unit 2,"+
68+
"O=COMPANY,POSTALCODE=17121,STREET=7 Street Ave,L=CITY,ST=TN,C=TC,DC=v1+DC=v2", ers3)
5669
}
5770

5871
func (suite *CertificateTestSuite) TestCertHasExpired() {
@@ -148,20 +161,20 @@ lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
148161
crt = ParseCert(commonCert)
149162
crt.Subject.CommonName = "localhost"
150163

151-
err1 := ValidateClientCertificate(crt, "127.0.0.1:8080")
164+
err1 := ValidateClientCertificate(crt, "127.0.0.1:8080", true)
152165

153166
suite.Nil(err1)
154167

155168
// mismatch
156169
crt = ParseCert(commonCert)
157170
crt.Subject.CommonName = "example.com"
158-
err2 := ValidateClientCertificate(crt, "127.0.0.1:8080")
171+
err2 := ValidateClientCertificate(crt, "127.0.0.1:8080", true)
159172
suite.Equal("x509: certificate is valid for example.com, not localhost", err2.Error())
160173

161174
// mismatch
162175
crt = ParseCert(commonCert)
163176
crt.Subject.CommonName = ""
164-
err3 := ValidateClientCertificate(crt, "127.0.0.1:8080")
177+
err3 := ValidateClientCertificate(crt, "127.0.0.1:8080", true)
165178
suite.Equal("x509: certificate is not valid for any names, but wanted to match localhost", err3.Error())
166179

167180
//mismatch
@@ -170,9 +183,12 @@ lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
170183
obj := asn1.ObjectIdentifier{2, 5, 29, 17}
171184
e1 := pkix.Extension{Id: obj, Critical: false, Value: []byte("")}
172185
crt.Extensions = append(crt.Extensions, e1)
173-
err4 := ValidateClientCertificate(crt, "127.0.0.1:8080")
186+
err4 := ValidateClientCertificate(crt, "127.0.0.1:8080", true)
174187
suite.Equal("x509: certificate is valid for COMODO RSA Domain Validation Secure Server CA, not localhost", err4.Error())
175188

189+
// false should skip verification and no error should be produced
190+
err5 := ValidateClientCertificate(crt, "127.0.0.1:8080", false)
191+
suite.Nil(err5)
176192
}
177193

178194
func (suite *CertificateTestSuite) TestFormatRdnToString() {

authmethods/authmethods_test.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,26 +133,33 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodFIndAll() {
133133

134134
// test the normal case
135135
amb1 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""}
136-
am1 := &ApiKeyAuthMethod{AccessKey: "access_key"}
137-
am1.BasicAuthMethod = amb1
136+
apiKeyAm := &ApiKeyAuthMethod{AccessKey: "access_key"}
137+
apiKeyAm.BasicAuthMethod = amb1
138138

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

143-
expAmList.AuthMethods = append(expAmList.AuthMethods, am1, am2)
143+
expAmList.AuthMethods = append(expAmList.AuthMethods, apiKeyAm, headersAm)
144144

145145
aMList, err1 := AuthMethodFindAll(mockstore)
146+
suite.Equal(2, len(aMList.AuthMethods))
147+
148+
for _, _am := range aMList.AuthMethods {
149+
_, ok := _am.(*ApiKeyAuthMethod)
150+
if ok {
151+
suite.Equal(apiKeyAm, _am)
152+
} else {
153+
suite.Equal(headersAm, _am)
154+
}
155+
}
156+
157+
suite.Nil(err1)
146158

147159
// empty list
148160
mockstore.AuthMethods = []stores.QAuthMethod{}
149161
aMList2, err2 := AuthMethodFindAll(mockstore)
150-
151-
suite.Equal(am1, aMList.AuthMethods[0])
152-
suite.Equal(am2, aMList.AuthMethods[1])
153162
suite.Equal(0, len(aMList2.AuthMethods))
154-
155-
suite.Nil(err1)
156163
suite.Nil(err2)
157164
}
158165

0 commit comments

Comments
 (0)