2019-03-11 19:48:46 +00:00
package provisioner
import (
2019-07-30 01:24:34 +00:00
"context"
2019-08-01 00:04:17 +00:00
"crypto"
2019-09-11 00:04:13 +00:00
"crypto/rand"
"crypto/rsa"
2019-03-11 19:48:46 +00:00
"crypto/x509"
2022-03-30 08:22:22 +00:00
"errors"
2019-03-15 20:49:50 +00:00
"fmt"
2019-12-20 21:30:05 +00:00
"net/http"
2019-03-11 19:48:46 +00:00
"strings"
"testing"
"time"
2023-02-23 12:43:13 +00:00
"github.com/stretchr/testify/require"
2020-08-24 21:44:11 +00:00
"go.step.sm/crypto/jose"
2022-03-30 08:22:22 +00:00
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
2019-03-11 19:48:46 +00:00
)
func Test_openIDConfiguration_Validate ( t * testing . T ) {
type fields struct {
Issuer string
JWKSetURI string
}
tests := [ ] struct {
name string
fields fields
wantErr bool
} {
{ "ok" , fields { "the-issuer" , "the-jwks-uri" } , false } ,
{ "no-issuer" , fields { "" , "the-jwks-uri" } , true } ,
{ "no-jwks-uri" , fields { "the-issuer" , "" } , true } ,
{ "empty" , fields { "" , "" } , true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := openIDConfiguration {
Issuer : tt . fields . Issuer ,
JWKSetURI : tt . fields . JWKSetURI ,
}
if err := c . Validate ( ) ; ( err != nil ) != tt . wantErr {
t . Errorf ( "openIDConfiguration.Validate() error = %v, wantErr %v" , err , tt . wantErr )
}
} )
}
}
func TestOIDC_Getters ( t * testing . T ) {
p , err := generateOIDC ( )
assert . FatalError ( t , err )
if got := p . GetID ( ) ; got != p . ClientID {
t . Errorf ( "OIDC.GetID() = %v, want %v" , got , p . ClientID )
}
if got := p . GetName ( ) ; got != p . Name {
t . Errorf ( "OIDC.GetName() = %v, want %v" , got , p . Name )
}
if got := p . GetType ( ) ; got != TypeOIDC {
t . Errorf ( "OIDC.GetType() = %v, want %v" , got , TypeOIDC )
}
kid , key , ok := p . GetEncryptedKey ( )
if kid != "" || key != "" || ok == true {
t . Errorf ( "OIDC.GetEncryptedKey() = (%v, %v, %v), want (%v, %v, %v)" ,
kid , key , ok , "" , "" , false )
}
}
func TestOIDC_Init ( t * testing . T ) {
srv := generateJWKServer ( 2 )
defer srv . Close ( )
config := Config {
Claims : globalProvisionerClaims ,
}
2019-03-19 22:27:41 +00:00
badClaims := & Claims {
DefaultTLSDur : & Duration { 0 } ,
}
2019-03-11 19:48:46 +00:00
type fields struct {
Type string
Name string
ClientID string
2019-03-15 18:10:52 +00:00
ClientSecret string
2019-03-11 19:48:46 +00:00
ConfigurationEndpoint string
Claims * Claims
Admins [ ] string
2019-03-15 20:49:50 +00:00
Domains [ ] string
2019-09-19 01:08:26 +00:00
ListenAddress string
2019-03-11 19:48:46 +00:00
}
type args struct {
config Config
}
tests := [ ] struct {
name string
fields fields
args args
wantErr bool
} {
2019-09-19 01:08:26 +00:00
{ "ok" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL , nil , nil , nil , "" } , args { config } , false } ,
{ "ok-admins" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL + "/.well-known/openid-configuration" , nil , [ ] string { "foo@smallstep.com" } , nil , "" } , args { config } , false } ,
{ "ok-domains" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL , nil , nil , [ ] string { "smallstep.com" } , "" } , args { config } , false } ,
{ "ok-listen-port" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL , nil , nil , nil , ":10000" } , args { config } , false } ,
{ "ok-listen-host-port" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL , nil , nil , nil , "127.0.0.1:10000" } , args { config } , false } ,
{ "ok-no-secret" , fields { "oidc" , "name" , "client-id" , "" , srv . URL , nil , nil , nil , "" } , args { config } , false } ,
{ "no-name" , fields { "oidc" , "" , "client-id" , "client-secret" , srv . URL , nil , nil , nil , "" } , args { config } , true } ,
{ "no-type" , fields { "" , "name" , "client-id" , "client-secret" , srv . URL , nil , nil , nil , "" } , args { config } , true } ,
{ "no-client-id" , fields { "oidc" , "name" , "" , "client-secret" , srv . URL , nil , nil , nil , "" } , args { config } , true } ,
{ "no-configuration" , fields { "oidc" , "name" , "client-id" , "client-secret" , "" , nil , nil , nil , "" } , args { config } , true } ,
{ "bad-configuration" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL + "/random" , nil , nil , nil , "" } , args { config } , true } ,
{ "bad-claims" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL + "/.well-known/openid-configuration" , badClaims , nil , nil , "" } , args { config } , true } ,
{ "bad-parse-url" , fields { "oidc" , "name" , "client-id" , "client-secret" , ":" , nil , nil , nil , "" } , args { config } , true } ,
{ "bad-get-url" , fields { "oidc" , "name" , "client-id" , "client-secret" , "https://" , nil , nil , nil , "" } , args { config } , true } ,
{ "bad-listen-address" , fields { "oidc" , "name" , "client-id" , "client-secret" , srv . URL , nil , nil , nil , "127.0.0.1" } , args { config } , true } ,
2019-03-11 19:48:46 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
p := & OIDC {
Type : tt . fields . Type ,
Name : tt . fields . Name ,
ClientID : tt . fields . ClientID ,
ConfigurationEndpoint : tt . fields . ConfigurationEndpoint ,
Claims : tt . fields . Claims ,
Admins : tt . fields . Admins ,
2019-09-19 01:08:26 +00:00
Domains : tt . fields . Domains ,
ListenAddress : tt . fields . ListenAddress ,
2019-03-11 19:48:46 +00:00
}
if err := p . Init ( tt . args . config ) ; ( err != nil ) != tt . wantErr {
t . Errorf ( "OIDC.Init() error = %v, wantErr %v" , err , tt . wantErr )
2019-09-19 00:13:58 +00:00
return
2019-03-11 19:48:46 +00:00
}
if tt . wantErr == false {
assert . Len ( t , 2 , p . keyStore . keySet . Keys )
assert . Equals ( t , openIDConfiguration {
Issuer : "the-issuer" ,
JWKSetURI : srv . URL + "/jwks_uri" ,
} , p . configuration )
}
} )
}
}
2019-03-05 08:07:13 +00:00
func TestOIDC_authorizeToken ( t * testing . T ) {
2020-04-24 21:36:32 +00:00
srv := generateJWKServer ( 3 )
2019-03-11 19:48:46 +00:00
defer srv . Close ( )
var keys jose . JSONWebKeySet
assert . FatalError ( t , getAndDecode ( srv . URL + "/private" , & keys ) )
2020-04-24 21:36:32 +00:00
issuer := "the-issuer"
tenantID := "ab800f7d-2c87-45fb-b1d0-f90d0bc5ec25"
tenantIssuer := "https://login.microsoftonline.com/" + tenantID + "/v2.0"
2019-03-11 19:48:46 +00:00
// Create test provisioners
p1 , err := generateOIDC ( )
assert . FatalError ( t , err )
p2 , err := generateOIDC ( )
assert . FatalError ( t , err )
p3 , err := generateOIDC ( )
assert . FatalError ( t , err )
2020-04-24 21:36:32 +00:00
// TenantID
p2 . TenantID = tenantID
2019-03-15 20:49:50 +00:00
// Admin + Domains
p3 . Admins = [ ] string { "name@smallstep.com" , "root@example.com" }
p3 . Domains = [ ] string { "smallstep.com" }
2019-03-11 19:48:46 +00:00
// Update configuration endpoints and initialize
config := Config { Claims : globalProvisionerClaims }
p1 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
2020-04-24 21:36:32 +00:00
p2 . ConfigurationEndpoint = srv . URL + "/common/.well-known/openid-configuration"
2019-03-11 19:48:46 +00:00
p3 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
assert . FatalError ( t , p1 . Init ( config ) )
assert . FatalError ( t , p2 . Init ( config ) )
assert . FatalError ( t , p3 . Init ( config ) )
2020-04-24 21:36:32 +00:00
t1 , err := generateSimpleToken ( issuer , p1 . ClientID , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
t2 , err := generateSimpleToken ( tenantIssuer , p2 . ClientID , & keys . Keys [ 1 ] )
assert . FatalError ( t , err )
t3 , err := generateToken ( "subject" , issuer , p3 . ClientID , "name@smallstep.com" , [ ] string { } , time . Now ( ) , & keys . Keys [ 2 ] )
2019-03-11 19:48:46 +00:00
assert . FatalError ( t , err )
2020-04-24 21:36:32 +00:00
t4 , err := generateToken ( "subject" , issuer , p3 . ClientID , "foo@smallstep.com" , [ ] string { } , time . Now ( ) , & keys . Keys [ 2 ] )
2019-03-11 19:48:46 +00:00
assert . FatalError ( t , err )
2020-07-21 02:01:43 +00:00
t5 , err := generateToken ( "subject" , issuer , p3 . ClientID , "" , [ ] string { } , time . Now ( ) , & keys . Keys [ 2 ] )
2019-03-15 20:49:50 +00:00
assert . FatalError ( t , err )
2020-07-21 02:01:43 +00:00
// Invalid email
2020-04-24 21:36:32 +00:00
failDomain , err := generateToken ( "subject" , issuer , p3 . ClientID , "name@example.com" , [ ] string { } , time . Now ( ) , & keys . Keys [ 2 ] )
2019-03-15 20:49:50 +00:00
assert . FatalError ( t , err )
2019-03-11 19:48:46 +00:00
// Invalid tokens
parts := strings . Split ( t1 , "." )
key , err := generateJSONWebKey ( )
assert . FatalError ( t , err )
// missing key
2020-04-24 21:36:32 +00:00
failKey , err := generateSimpleToken ( issuer , p1 . ClientID , key )
2019-03-11 19:48:46 +00:00
assert . FatalError ( t , err )
// invalid token
failTok := "foo." + parts [ 1 ] + "." + parts [ 2 ]
// invalid claims
failClaims := parts [ 0 ] + ".foo." + parts [ 1 ]
// invalid issuer
failIss , err := generateSimpleToken ( "bad-issuer" , p1 . ClientID , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
// invalid audience
2020-04-24 21:36:32 +00:00
failAud , err := generateSimpleToken ( issuer , "foobar" , & keys . Keys [ 0 ] )
2019-03-11 19:48:46 +00:00
assert . FatalError ( t , err )
// invalid signature
failSig := t1 [ 0 : len ( t1 ) - 2 ]
// expired
2020-04-24 21:36:32 +00:00
failExp , err := generateToken ( "subject" , issuer , p1 . ClientID , "name@smallstep.com" , [ ] string { } , time . Now ( ) . Add ( - 360 * time . Second ) , & keys . Keys [ 0 ] )
2019-03-11 19:48:46 +00:00
assert . FatalError ( t , err )
// not before
2020-04-24 21:36:32 +00:00
failNbf , err := generateToken ( "subject" , issuer , p1 . ClientID , "name@smallstep.com" , [ ] string { } , time . Now ( ) . Add ( 360 * time . Second ) , & keys . Keys [ 0 ] )
2019-03-11 19:48:46 +00:00
assert . FatalError ( t , err )
type args struct {
token string
}
tests := [ ] struct {
2020-04-24 21:36:32 +00:00
name string
prov * OIDC
args args
code int
wantIssuer string
2023-02-23 12:43:13 +00:00
expErr error
2019-03-11 19:48:46 +00:00
} {
2023-02-23 12:43:13 +00:00
{ "ok1" , p1 , args { t1 } , http . StatusOK , issuer , nil } ,
{ "ok tenantid" , p2 , args { t2 } , http . StatusOK , tenantIssuer , nil } ,
{ "ok admin" , p3 , args { t3 } , http . StatusOK , issuer , nil } ,
{ "ok domain" , p3 , args { t4 } , http . StatusOK , issuer , nil } ,
{ "ok no email" , p3 , args { t5 } , http . StatusOK , issuer , nil } ,
{ "fail-domain" , p3 , args { failDomain } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: email "name@example.com" is not allowed ` ) } ,
{ "fail-key" , p1 , args { failKey } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken; cannot validate oidc token ` ) } ,
{ "fail-token" , p1 , args { failTok } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken; error parsing oidc token: invalid character '~' looking for beginning of value ` ) } ,
{ "fail-claims" , p1 , args { failClaims } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken; error parsing oidc token claims: invalid character '~' looking for beginning of value ` ) } ,
{ "fail-issuer" , p1 , args { failIss } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, invalid issuer claim (iss) ` ) } ,
{ "fail-audience" , p1 , args { failAud } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, invalid audience claim (aud) ` ) } ,
{ "fail-signature" , p1 , args { failSig } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken; cannot validate oidc token ` ) } ,
{ "fail-expired" , p1 , args { failExp } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, token is expired (exp) ` ) } ,
{ "fail-not-before" , p1 , args { failNbf } , http . StatusUnauthorized , "" , errors . New ( ` oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, token not valid yet (nbf) ` ) } ,
2019-03-11 19:48:46 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2019-03-05 08:07:13 +00:00
got , err := tt . prov . authorizeToken ( tt . args . token )
2023-02-23 12:43:13 +00:00
if tt . expErr != nil {
require . Error ( t , err )
require . EqualError ( t , err , tt . expErr . Error ( ) )
2022-09-22 07:04:31 +00:00
var sc render . StatusCodedError
2023-02-23 12:43:13 +00:00
require . ErrorAs ( t , err , & sc , "error does not implement StatusCodedError interface" )
require . Equal ( t , tt . code , sc . StatusCode ( ) )
require . Nil ( t , got )
2019-03-05 08:07:13 +00:00
} else {
2023-02-23 12:43:13 +00:00
require . NotNil ( t , got )
require . Equal ( t , tt . wantIssuer , got . Issuer )
2019-03-05 08:07:13 +00:00
}
} )
}
}
func TestOIDC_AuthorizeSign ( t * testing . T ) {
srv := generateJWKServer ( 2 )
defer srv . Close ( )
var keys jose . JSONWebKeySet
assert . FatalError ( t , getAndDecode ( srv . URL + "/private" , & keys ) )
// Create test provisioners
p1 , err := generateOIDC ( )
assert . FatalError ( t , err )
p2 , err := generateOIDC ( )
assert . FatalError ( t , err )
p3 , err := generateOIDC ( )
assert . FatalError ( t , err )
// Admin + Domains
p3 . Admins = [ ] string { "name@smallstep.com" , "root@example.com" }
p3 . Domains = [ ] string { "smallstep.com" }
// Update configuration endpoints and initialize
config := Config { Claims : globalProvisionerClaims }
p1 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
p2 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
p3 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
assert . FatalError ( t , p1 . Init ( config ) )
assert . FatalError ( t , p2 . Init ( config ) )
assert . FatalError ( t , p3 . Init ( config ) )
t1 , err := generateSimpleToken ( "the-issuer" , p1 . ClientID , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
// Admin email not in domains
okAdmin , err := generateToken ( "subject" , "the-issuer" , p3 . ClientID , "root@example.com" , [ ] string { "test.smallstep.com" } , time . Now ( ) , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
2020-07-21 02:01:43 +00:00
// No email
noEmail , err := generateToken ( "subject" , "the-issuer" , p3 . ClientID , "" , [ ] string { } , time . Now ( ) , & keys . Keys [ 0 ] )
2019-03-05 08:07:13 +00:00
assert . FatalError ( t , err )
type args struct {
token string
}
tests := [ ] struct {
name string
prov * OIDC
args args
2019-12-20 21:30:05 +00:00
code int
2019-03-05 08:07:13 +00:00
wantErr bool
} {
2019-12-20 21:30:05 +00:00
{ "ok1" , p1 , args { t1 } , http . StatusOK , false } ,
{ "admin" , p3 , args { okAdmin } , http . StatusOK , false } ,
2020-07-21 02:01:43 +00:00
{ "no-email" , p3 , args { noEmail } , http . StatusOK , false } ,
{ "bad-token" , p3 , args { "foobar" } , http . StatusUnauthorized , true } ,
2019-03-05 08:07:13 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2019-12-20 21:30:05 +00:00
got , err := tt . prov . AuthorizeSign ( context . Background ( ) , tt . args . token )
2019-03-11 19:48:46 +00:00
if ( err != nil ) != tt . wantErr {
t . Errorf ( "OIDC.Authorize() error = %v, wantErr %v" , err , tt . wantErr )
return
}
if err != nil {
2022-09-22 07:04:31 +00:00
var sc render . StatusCodedError
assert . Fatal ( t , errors . As ( err , & sc ) , "error does not implement StatusCodedError interface" )
2019-12-20 21:30:05 +00:00
assert . Equals ( t , sc . StatusCode ( ) , tt . code )
2019-03-11 19:48:46 +00:00
assert . Nil ( t , got )
2021-10-08 18:59:57 +00:00
} else if assert . NotNil ( t , got ) {
2022-09-30 00:16:26 +00:00
assert . Equals ( t , 8 , len ( got ) )
2021-10-08 18:59:57 +00:00
for _ , o := range got {
switch v := o . ( type ) {
2022-03-28 22:06:56 +00:00
case * OIDC :
2021-10-08 18:59:57 +00:00
case certificateOptionsFunc :
case * provisionerExtensionOption :
2022-03-11 22:59:42 +00:00
assert . Equals ( t , v . Type , TypeOIDC )
2021-10-08 18:59:57 +00:00
assert . Equals ( t , v . Name , tt . prov . GetName ( ) )
assert . Equals ( t , v . CredentialID , tt . prov . ClientID )
assert . Len ( t , 0 , v . KeyValuePairs )
case profileDefaultDuration :
2022-03-10 02:43:45 +00:00
assert . Equals ( t , time . Duration ( v ) , tt . prov . ctl . Claimer . DefaultTLSCertDuration ( ) )
2021-10-08 18:59:57 +00:00
case defaultPublicKeyValidator :
case * validityValidator :
2022-03-10 02:43:45 +00:00
assert . Equals ( t , v . min , tt . prov . ctl . Claimer . MinTLSCertDuration ( ) )
assert . Equals ( t , v . max , tt . prov . ctl . Claimer . MaxTLSCertDuration ( ) )
2021-10-08 18:59:57 +00:00
case emailOnlyIdentity :
assert . Equals ( t , string ( v ) , "name@smallstep.com" )
2022-01-03 11:25:24 +00:00
case * x509NamePolicyValidator :
assert . Equals ( t , nil , v . policyEngine )
2022-09-30 00:16:26 +00:00
case * WebhookController :
assert . Len ( t , 0 , v . webhooks )
2021-10-08 18:59:57 +00:00
default :
2022-03-30 08:22:22 +00:00
assert . FatalError ( t , fmt . Errorf ( "unexpected sign option of type %T" , v ) )
2019-09-05 01:31:09 +00:00
}
2019-03-11 19:48:46 +00:00
}
}
} )
}
}
2019-12-20 21:30:05 +00:00
func TestOIDC_AuthorizeRevoke ( t * testing . T ) {
srv := generateJWKServer ( 2 )
defer srv . Close ( )
var keys jose . JSONWebKeySet
assert . FatalError ( t , getAndDecode ( srv . URL + "/private" , & keys ) )
// Create test provisioners
p1 , err := generateOIDC ( )
assert . FatalError ( t , err )
p3 , err := generateOIDC ( )
assert . FatalError ( t , err )
// Admin + Domains
p3 . Admins = [ ] string { "name@smallstep.com" , "root@example.com" }
p3 . Domains = [ ] string { "smallstep.com" }
// Update configuration endpoints and initialize
config := Config { Claims : globalProvisionerClaims }
p1 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
p3 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
assert . FatalError ( t , p1 . Init ( config ) )
assert . FatalError ( t , p3 . Init ( config ) )
t1 , err := generateSimpleToken ( "the-issuer" , p1 . ClientID , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
// Admin email not in domains
okAdmin , err := generateToken ( "subject" , "the-issuer" , p3 . ClientID , "root@example.com" , [ ] string { "test.smallstep.com" } , time . Now ( ) , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
// Invalid email
failEmail , err := generateToken ( "subject" , "the-issuer" , p3 . ClientID , "" , [ ] string { } , time . Now ( ) , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
type args struct {
token string
}
tests := [ ] struct {
name string
prov * OIDC
args args
code int
wantErr bool
} {
{ "ok1" , p1 , args { t1 } , http . StatusUnauthorized , true } ,
{ "admin" , p3 , args { okAdmin } , http . StatusOK , false } ,
{ "fail-email" , p3 , args { failEmail } , http . StatusUnauthorized , true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
err := tt . prov . AuthorizeRevoke ( context . Background ( ) , tt . args . token )
if ( err != nil ) != tt . wantErr {
fmt . Println ( tt )
t . Errorf ( "OIDC.Authorize() error = %v, wantErr %v" , err , tt . wantErr )
return
} else if err != nil {
2022-09-22 07:04:31 +00:00
var sc render . StatusCodedError
assert . Fatal ( t , errors . As ( err , & sc ) , "error does not implement StatusCodedError interface" )
2019-12-20 21:30:05 +00:00
assert . Equals ( t , sc . StatusCode ( ) , tt . code )
}
} )
}
}
func TestOIDC_AuthorizeRenew ( t * testing . T ) {
2022-03-10 18:46:28 +00:00
now := time . Now ( ) . Truncate ( time . Second )
2019-12-20 21:30:05 +00:00
p1 , err := generateOIDC ( )
assert . FatalError ( t , err )
p2 , err := generateOIDC ( )
assert . FatalError ( t , err )
// disable renewal
disable := true
p2 . Claims = & Claims { DisableRenewal : & disable }
2022-03-10 02:43:45 +00:00
p2 . ctl . Claimer , err = NewClaimer ( p2 . Claims , globalProvisionerClaims )
2019-12-20 21:30:05 +00:00
assert . FatalError ( t , err )
type args struct {
cert * x509 . Certificate
}
tests := [ ] struct {
name string
prov * OIDC
args args
code int
wantErr bool
} {
2022-03-10 02:43:45 +00:00
{ "ok" , p1 , args { & x509 . Certificate {
NotBefore : now ,
NotAfter : now . Add ( time . Hour ) ,
} } , http . StatusOK , false } ,
{ "fail/renew-disabled" , p2 , args { & x509 . Certificate {
NotBefore : now ,
NotAfter : now . Add ( time . Hour ) ,
} } , http . StatusUnauthorized , true } ,
2019-12-20 21:30:05 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
err := tt . prov . AuthorizeRenew ( context . Background ( ) , tt . args . cert )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "OIDC.AuthorizeRenew() error = %v, wantErr %v" , err , tt . wantErr )
} else if err != nil {
2022-09-22 07:04:31 +00:00
var sc render . StatusCodedError
assert . Fatal ( t , errors . As ( err , & sc ) , "error does not implement StatusCodedError interface" )
2019-12-20 21:30:05 +00:00
assert . Equals ( t , sc . StatusCode ( ) , tt . code )
}
} )
}
}
2019-11-14 23:26:37 +00:00
func TestOIDC_AuthorizeSSHSign ( t * testing . T ) {
2019-08-01 00:04:17 +00:00
tm , fn := mockNow ( )
defer fn ( )
srv := generateJWKServer ( 2 )
defer srv . Close ( )
var keys jose . JSONWebKeySet
assert . FatalError ( t , getAndDecode ( srv . URL + "/private" , & keys ) )
// Create test provisioners
p1 , err := generateOIDC ( )
assert . FatalError ( t , err )
p2 , err := generateOIDC ( )
assert . FatalError ( t , err )
p3 , err := generateOIDC ( )
assert . FatalError ( t , err )
2019-11-16 00:57:51 +00:00
p4 , err := generateOIDC ( )
assert . FatalError ( t , err )
p5 , err := generateOIDC ( )
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
p6 , err := generateOIDC ( )
assert . FatalError ( t , err )
2019-08-01 00:04:17 +00:00
// Admin + Domains
p3 . Admins = [ ] string { "name@smallstep.com" , "root@example.com" }
p3 . Domains = [ ] string { "smallstep.com" }
2019-12-20 21:30:05 +00:00
// disable sshCA
disable := false
p6 . Claims = & Claims { EnableSSHCA : & disable }
2022-03-10 02:43:45 +00:00
p6 . ctl . Claimer , err = NewClaimer ( p6 . Claims , globalProvisionerClaims )
2019-12-20 21:30:05 +00:00
assert . FatalError ( t , err )
2019-08-01 00:04:17 +00:00
// Update configuration endpoints and initialize
config := Config { Claims : globalProvisionerClaims }
p1 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
p2 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
p3 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
2019-11-16 00:57:51 +00:00
p4 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
p5 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
2019-08-01 00:04:17 +00:00
assert . FatalError ( t , p1 . Init ( config ) )
assert . FatalError ( t , p2 . Init ( config ) )
assert . FatalError ( t , p3 . Init ( config ) )
2019-11-16 00:57:51 +00:00
assert . FatalError ( t , p4 . Init ( config ) )
assert . FatalError ( t , p5 . Init ( config ) )
2022-03-10 02:43:45 +00:00
p4 . ctl . IdentityFunc = func ( ctx context . Context , p Interface , email string ) ( * Identity , error ) {
2019-11-16 00:57:51 +00:00
return & Identity { Usernames : [ ] string { "max" , "mariano" } } , nil
}
2022-03-10 02:43:45 +00:00
p5 . ctl . IdentityFunc = func ( ctx context . Context , p Interface , email string ) ( * Identity , error ) {
2019-11-16 00:57:51 +00:00
return nil , errors . New ( "force" )
}
2021-04-30 00:14:28 +00:00
// Additional test needed for empty usernames and duplicate email and usernames
2019-08-01 00:04:17 +00:00
t1 , err := generateSimpleToken ( "the-issuer" , p1 . ClientID , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
2019-11-16 00:57:51 +00:00
okGetIdentityToken , err := generateSimpleToken ( "the-issuer" , p4 . ClientID , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
failGetIdentityToken , err := generateSimpleToken ( "the-issuer" , p5 . ClientID , & keys . Keys [ 0 ] )
assert . FatalError ( t , err )
2019-08-01 00:04:17 +00:00
// Admin email not in domains
2021-04-30 01:30:26 +00:00
okAdmin , err := generateOIDCToken ( "subject" , "the-issuer" , p3 . ClientID , "root@example.com" , "" , time . Now ( ) , & keys . Keys [ 0 ] )
2019-08-01 00:04:17 +00:00
assert . FatalError ( t , err )
2020-07-21 02:01:43 +00:00
// Empty email
2022-09-06 23:45:27 +00:00
emptyEmail , err := generateToken ( "subject" , "the-issuer" , p1 . ClientID , "" , [ ] string { } , time . Now ( ) , & keys . Keys [ 0 ] )
expectemptyEmailOptions := & SignSSHOptions {
CertType : "user" ,
Principals : [ ] string { } ,
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( p1 . ctl . Claimer . DefaultUserSSHCertDuration ( ) ) ) ,
}
2019-08-01 00:04:17 +00:00
assert . FatalError ( t , err )
key , err := generateJSONWebKey ( )
assert . FatalError ( t , err )
signer , err := generateJSONWebKey ( )
assert . FatalError ( t , err )
2019-09-11 00:04:13 +00:00
pub := key . Public ( ) . Key
rsa2048 , err := rsa . GenerateKey ( rand . Reader , 2048 )
assert . FatalError ( t , err )
2022-08-23 19:43:48 +00:00
//nolint:gosec // tests minimum size of the key
2019-09-11 00:04:13 +00:00
rsa1024 , err := rsa . GenerateKey ( rand . Reader , 1024 )
assert . FatalError ( t , err )
2022-03-10 02:43:45 +00:00
userDuration := p1 . ctl . Claimer . DefaultUserSSHCertDuration ( )
hostDuration := p1 . ctl . Claimer . DefaultHostSSHCertDuration ( )
2020-07-23 01:24:45 +00:00
expectedUserOptions := & SignSSHOptions {
2019-11-16 00:57:51 +00:00
CertType : "user" , Principals : [ ] string { "name" , "name@smallstep.com" } ,
2019-08-01 00:04:17 +00:00
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) ,
}
2020-07-23 01:24:45 +00:00
expectedAdminOptions := & SignSSHOptions {
2019-11-16 00:57:51 +00:00
CertType : "user" , Principals : [ ] string { "root" , "root@example.com" } ,
2019-08-01 00:04:17 +00:00
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) ,
}
2020-07-23 01:24:45 +00:00
expectedHostOptions := & SignSSHOptions {
2019-08-01 00:04:17 +00:00
CertType : "host" , Principals : [ ] string { "smallstep.com" } ,
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( hostDuration ) ) ,
}
type args struct {
token string
2020-07-23 01:24:45 +00:00
sshOpts SignSSHOptions
2019-09-11 00:04:13 +00:00
key interface { }
2019-08-01 00:04:17 +00:00
}
tests := [ ] struct {
name string
prov * OIDC
args args
2020-07-23 01:24:45 +00:00
expected * SignSSHOptions
2019-12-20 21:30:05 +00:00
code int
2019-08-01 00:04:17 +00:00
wantErr bool
wantSignErr bool
} {
2020-07-23 01:24:45 +00:00
{ "ok" , p1 , args { t1 , SignSSHOptions { } , pub } , expectedUserOptions , http . StatusOK , false , false } ,
{ "ok-rsa2048" , p1 , args { t1 , SignSSHOptions { } , rsa2048 . Public ( ) } , expectedUserOptions , http . StatusOK , false , false } ,
{ "ok-user" , p1 , args { t1 , SignSSHOptions { CertType : "user" } , pub } , expectedUserOptions , http . StatusOK , false , false } ,
2022-09-06 23:45:27 +00:00
{ "ok-empty-email" , p1 , args { emptyEmail , SignSSHOptions { CertType : "user" } , pub } , expectemptyEmailOptions , http . StatusOK , false , false } ,
2020-07-23 01:24:45 +00:00
{ "ok-principals" , p1 , args { t1 , SignSSHOptions { Principals : [ ] string { "name" } } , pub } ,
2020-08-04 01:10:29 +00:00
& SignSSHOptions { CertType : "user" , Principals : [ ] string { "name" , "name@smallstep.com" } ,
2019-12-20 21:30:05 +00:00
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) } , http . StatusOK , false , false } ,
2022-12-15 01:51:50 +00:00
{ "ok-principals-ignore-passed" , p1 , args { t1 , SignSSHOptions { Principals : [ ] string { "root" } } , pub } ,
& SignSSHOptions { CertType : "user" , Principals : [ ] string { "name" , "name@smallstep.com" } ,
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) } , http . StatusOK , false , false } ,
2020-07-23 01:24:45 +00:00
{ "ok-principals-getIdentity" , p4 , args { okGetIdentityToken , SignSSHOptions { Principals : [ ] string { "mariano" } } , pub } ,
2020-08-04 01:10:29 +00:00
& SignSSHOptions { CertType : "user" , Principals : [ ] string { "max" , "mariano" } ,
2019-12-20 21:30:05 +00:00
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) } , http . StatusOK , false , false } ,
2020-07-23 01:24:45 +00:00
{ "ok-emptyPrincipals-getIdentity" , p4 , args { okGetIdentityToken , SignSSHOptions { } , pub } ,
& SignSSHOptions { CertType : "user" , Principals : [ ] string { "max" , "mariano" } ,
2019-12-20 21:30:05 +00:00
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) } , http . StatusOK , false , false } ,
2020-07-23 01:24:45 +00:00
{ "ok-options" , p1 , args { t1 , SignSSHOptions { CertType : "user" , Principals : [ ] string { "name" } } , pub } ,
2020-08-04 01:10:29 +00:00
& SignSSHOptions { CertType : "user" , Principals : [ ] string { "name" , "name@smallstep.com" } ,
2019-12-20 21:30:05 +00:00
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) } , http . StatusOK , false , false } ,
2021-04-30 01:30:26 +00:00
{ "ok-admin-user" , p3 , args { okAdmin , SignSSHOptions { CertType : "user" , KeyID : "root@example.com" , Principals : [ ] string { "root" , "root@example.com" } } , pub } ,
2020-08-04 01:10:29 +00:00
expectedAdminOptions , http . StatusOK , false , false } ,
2021-04-30 01:30:26 +00:00
{ "ok-admin-host" , p3 , args { okAdmin , SignSSHOptions { CertType : "host" , KeyID : "smallstep.com" , Principals : [ ] string { "smallstep.com" } } , pub } ,
2020-08-04 01:10:29 +00:00
expectedHostOptions , http . StatusOK , false , false } ,
2021-04-30 01:30:26 +00:00
{ "ok-admin-options" , p3 , args { okAdmin , SignSSHOptions { CertType : "user" , KeyID : "name" , Principals : [ ] string { "name" } } , pub } ,
2020-07-23 01:24:45 +00:00
& SignSSHOptions { CertType : "user" , Principals : [ ] string { "name" } ,
2019-12-20 21:30:05 +00:00
ValidAfter : NewTimeDuration ( tm ) , ValidBefore : NewTimeDuration ( tm . Add ( userDuration ) ) } , http . StatusOK , false , false } ,
2020-07-23 01:24:45 +00:00
{ "fail-rsa1024" , p1 , args { t1 , SignSSHOptions { } , rsa1024 . Public ( ) } , expectedUserOptions , http . StatusOK , false , true } ,
{ "fail-user-host" , p1 , args { t1 , SignSSHOptions { CertType : "host" } , pub } , nil , http . StatusOK , false , true } ,
{ "fail-getIdentity" , p5 , args { failGetIdentityToken , SignSSHOptions { } , pub } , nil , http . StatusInternalServerError , true , false } ,
{ "fail-sshCA-disabled" , p6 , args { "foo" , SignSSHOptions { } , pub } , nil , http . StatusUnauthorized , true , false } ,
2020-08-04 01:10:29 +00:00
// Missing parametrs
{ "fail-admin-type" , p3 , args { okAdmin , SignSSHOptions { KeyID : "root@example.com" , Principals : [ ] string { "root@example.com" } } , pub } , nil , http . StatusUnauthorized , false , true } ,
{ "fail-admin-key-id" , p3 , args { okAdmin , SignSSHOptions { CertType : "user" , Principals : [ ] string { "root@example.com" } } , pub } , nil , http . StatusUnauthorized , false , true } ,
{ "fail-admin-principals" , p3 , args { okAdmin , SignSSHOptions { CertType : "user" , KeyID : "root@example.com" } , pub } , nil , http . StatusUnauthorized , false , true } ,
2019-08-01 00:04:17 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2019-12-20 21:30:05 +00:00
got , err := tt . prov . AuthorizeSSHSign ( context . Background ( ) , tt . args . token )
2019-08-01 00:04:17 +00:00
if ( err != nil ) != tt . wantErr {
2019-11-14 23:26:37 +00:00
t . Errorf ( "OIDC.AuthorizeSSHSign() error = %v, wantErr %v" , err , tt . wantErr )
2019-08-01 00:04:17 +00:00
return
}
if err != nil {
2022-09-22 07:04:31 +00:00
var sc render . StatusCodedError
assert . Fatal ( t , errors . As ( err , & sc ) , "error does not implement StatusCodedError interface" )
2019-12-20 21:30:05 +00:00
assert . Equals ( t , sc . StatusCode ( ) , tt . code )
2019-08-01 00:04:17 +00:00
assert . Nil ( t , got )
} else if assert . NotNil ( t , got ) {
2019-09-11 00:04:13 +00:00
cert , err := signSSHCertificate ( tt . args . key , tt . args . sshOpts , got , signer . Key . ( crypto . Signer ) )
2019-08-01 00:04:17 +00:00
if ( err != nil ) != tt . wantSignErr {
t . Errorf ( "SignSSH error = %v, wantSignErr %v" , err , tt . wantSignErr )
} else {
if tt . wantSignErr {
assert . Nil ( t , cert )
} else {
assert . NoError ( t , validateSSHCertificate ( cert , tt . expected ) )
}
}
}
} )
}
}
2019-12-20 21:30:05 +00:00
func TestOIDC_AuthorizeSSHRevoke ( t * testing . T ) {
p1 , err := generateOIDC ( )
assert . FatalError ( t , err )
p2 , err := generateOIDC ( )
assert . FatalError ( t , err )
p2 . Admins = [ ] string { "root@example.com" }
2019-03-05 08:07:13 +00:00
srv := generateJWKServer ( 2 )
defer srv . Close ( )
var keys jose . JSONWebKeySet
assert . FatalError ( t , getAndDecode ( srv . URL + "/private" , & keys ) )
config := Config { Claims : globalProvisionerClaims }
p1 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
2019-12-20 21:30:05 +00:00
p2 . ConfigurationEndpoint = srv . URL + "/.well-known/openid-configuration"
2019-03-05 08:07:13 +00:00
assert . FatalError ( t , p1 . Init ( config ) )
2019-12-20 21:30:05 +00:00
assert . FatalError ( t , p2 . Init ( config ) )
2019-03-05 08:07:13 +00:00
2019-12-20 21:30:05 +00:00
// Invalid email
failEmail , err := generateToken ( "subject" , "the-issuer" , p1 . ClientID , "" , [ ] string { } , time . Now ( ) , & keys . Keys [ 0 ] )
2019-03-05 08:07:13 +00:00
assert . FatalError ( t , err )
// Admin email not in domains
2019-12-20 21:30:05 +00:00
noAdmin , err := generateToken ( "subject" , "the-issuer" , p1 . ClientID , "root@example.com" , [ ] string { "test.smallstep.com" } , time . Now ( ) , & keys . Keys [ 0 ] )
2019-03-05 08:07:13 +00:00
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
// Admin email in domains
okAdmin , err := generateToken ( "subject" , "the-issuer" , p2 . ClientID , "root@example.com" , [ ] string { "test.smallstep.com" } , time . Now ( ) , & keys . Keys [ 0 ] )
2019-03-05 08:07:13 +00:00
assert . FatalError ( t , err )
type args struct {
token string
}
tests := [ ] struct {
name string
prov * OIDC
args args
2019-12-20 21:30:05 +00:00
code int
2019-03-05 08:07:13 +00:00
wantErr bool
} {
2019-12-20 21:30:05 +00:00
{ "ok" , p2 , args { okAdmin } , http . StatusOK , false } ,
{ "fail/invalid-token" , p1 , args { failEmail } , http . StatusUnauthorized , true } ,
{ "fail/not-admin" , p1 , args { noAdmin } , http . StatusUnauthorized , true } ,
2019-03-05 08:07:13 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2019-12-20 21:30:05 +00:00
err := tt . prov . AuthorizeSSHRevoke ( context . Background ( ) , tt . args . token )
2019-03-05 08:07:13 +00:00
if ( err != nil ) != tt . wantErr {
2019-12-20 21:30:05 +00:00
t . Errorf ( "OIDC.AuthorizeSSHRevoke() error = %v, wantErr %v" , err , tt . wantErr )
} else if err != nil {
2022-09-22 07:04:31 +00:00
var sc render . StatusCodedError
assert . Fatal ( t , errors . As ( err , & sc ) , "error does not implement StatusCodedError interface" )
2019-12-20 21:30:05 +00:00
assert . Equals ( t , sc . StatusCode ( ) , tt . code )
2019-03-11 19:48:46 +00:00
}
} )
}
}
2019-03-15 20:49:50 +00:00
func Test_sanitizeEmail ( t * testing . T ) {
tests := [ ] struct {
name string
email string
want string
} {
{ "equal" , "name@smallstep.com" , "name@smallstep.com" } ,
{ "domain-insensitive" , "name@SMALLSTEP.COM" , "name@smallstep.com" } ,
{ "local-sensitive" , "NaMe@smallSTEP.CoM" , "NaMe@smallstep.com" } ,
{ "multiple-@" , "NaMe@NaMe@smallSTEP.CoM" , "NaMe@NaMe@smallstep.com" } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := sanitizeEmail ( tt . email ) ; got != tt . want {
t . Errorf ( "sanitizeEmail() = %v, want %v" , got , tt . want )
}
} )
}
}
2021-09-23 22:49:28 +00:00
func Test_openIDPayload_IsAdmin ( t * testing . T ) {
type fields struct {
Email string
Groups [ ] string
}
type args struct {
admins [ ] string
}
tests := [ ] struct {
name string
fields fields
args args
want bool
} {
{ "ok email" , fields { "admin@smallstep.com" , nil } , args { [ ] string { "admin@smallstep.com" } } , true } ,
{ "ok email multiple" , fields { "admin@smallstep.com" , [ ] string { "admin" , "eng" } } , args { [ ] string { "eng@smallstep.com" , "admin@smallstep.com" } } , true } ,
{ "ok email sanitized" , fields { "admin@Smallstep.com" , nil } , args { [ ] string { "admin@smallStep.com" } } , true } ,
{ "ok group" , fields { "" , [ ] string { "admin" } } , args { [ ] string { "admin" } } , true } ,
{ "ok group multiple" , fields { "admin@smallstep.com" , [ ] string { "engineering" , "admin" } } , args { [ ] string { "admin" } } , true } ,
{ "fail missing" , fields { "eng@smallstep.com" , [ ] string { "admin" } } , args { [ ] string { "admin@smallstep.com" } } , false } ,
{ "fail email letter case" , fields { "Admin@smallstep.com" , [ ] string { } } , args { [ ] string { "admin@smallstep.com" } } , false } ,
{ "fail group letter case" , fields { "" , [ ] string { "Admin" } } , args { [ ] string { "admin" } } , false } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
o := & openIDPayload {
Email : tt . fields . Email ,
Groups : tt . fields . Groups ,
}
if got := o . IsAdmin ( tt . args . admins ) ; got != tt . want {
t . Errorf ( "openIDPayload.IsAdmin() = %v, want %v" , got , tt . want )
}
} )
}
}