Skip to content

Commit 2fb3336

Browse files
committed
authn: Add RSA signing and validation to authN service
Signed-off-by: Aaron Wilson <aawilson@nvidia.com>
1 parent 34c137f commit 2fb3336

16 files changed

Lines changed: 557 additions & 171 deletions

File tree

api/apc/urlpaths.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,12 @@ const (
4545

4646
// AuthN server endpoints (l2)
4747
const (
48-
Users = "users"
49-
Clusters = "clusters"
50-
Roles = "roles"
48+
Users = "users"
49+
Clusters = "clusters"
50+
Roles = "roles"
51+
OIDCPrefix = ".well-known"
52+
OIDCConfig = "openid-configuration"
53+
JWKS = "jwks.json"
5154
)
5255

5356
// l3 ---
@@ -197,6 +200,8 @@ var (
197200
URLPathUsers = urlpath(Version, Users)
198201
URLPathClusters = urlpath(Version, Clusters)
199202
URLPathRoles = urlpath(Version, Roles)
203+
URLPathOIDC = urlpath(Version, OIDCPrefix, OIDCConfig)
204+
URLPathJWKS = urlpath(Version, OIDCPrefix, JWKS)
200205

201206
URLPathML = urlpath(Version, ML)
202207
)

api/authn/authn.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/NVIDIA/aistore/cmn/cos"
1616

1717
jsoniter "github.com/json-iterator/go"
18+
"github.com/lestrrat-go/jwx/v2/jwk"
1819
)
1920

2021
func AddUser(bp api.BaseParams, newUser *User) error {
@@ -299,3 +300,29 @@ func SetConfig(bp api.BaseParams, conf *ConfigToUpdate) error {
299300
}
300301
return reqParams.DoRequest()
301302
}
303+
304+
func GetOIDCConfig(bp api.BaseParams) (*OIDCConfiguration, error) {
305+
bp.Method = http.MethodGet
306+
reqParams := api.AllocRp()
307+
defer api.FreeRp(reqParams)
308+
{
309+
reqParams.BaseParams = bp
310+
reqParams.Path = apc.URLPathOIDC.S
311+
}
312+
oidcConf := &OIDCConfiguration{}
313+
_, err := reqParams.DoReqAny(oidcConf)
314+
return oidcConf, err
315+
}
316+
317+
func GetJWKS(bp api.BaseParams) (jwk.Set, error) {
318+
bp.Method = http.MethodGet
319+
reqParams := api.AllocRp()
320+
defer api.FreeRp(reqParams)
321+
{
322+
reqParams.BaseParams = bp
323+
reqParams.Path = apc.URLPathJWKS.S
324+
}
325+
keySet := jwk.NewSet()
326+
_, err := reqParams.DoReqAny(keySet)
327+
return keySet, err
328+
}

api/authn/config.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package authn
66

77
import (
8+
"crypto/rsa"
89
"strconv"
910
"time"
1011

@@ -26,7 +27,8 @@ type (
2627
Level string `json:"level"`
2728
}
2829
NetConf struct {
29-
HTTP HTTPConf `json:"http"`
30+
ExternalURL string `json:"external_url"`
31+
HTTP HTTPConf `json:"http"`
3032
}
3133
HTTPConf struct {
3234
Certificate string `json:"server_crt"`
@@ -35,11 +37,14 @@ type (
3537
UseHTTPS bool `json:"use_https"`
3638
}
3739
ServerConf struct {
38-
psecret *string `json:"-"`
39-
pexpire *cos.Duration `json:"-"`
40-
Secret string `json:"secret"`
41-
Expire cos.Duration `json:"expiration_time"`
42-
PubKey *string `json:"public_key"`
40+
psecret *string `json:"-"`
41+
pexpire *cos.Duration `json:"-"`
42+
pKey *rsa.PrivateKey `json:"-"`
43+
Secret string `json:"secret"`
44+
// Determines when the secret or key expires
45+
// Also used to determine max-age for client caches of JWKS
46+
Expire cos.Duration `json:"expiration_time"`
47+
PubKey *string `json:"public_key"`
4348
}
4449
TimeoutConf struct {
4550
Default cos.Duration `json:"default_timeout"`
@@ -81,5 +86,7 @@ func (c *Config) Verbose() bool {
8186
return level > 3
8287
}
8388

84-
func (c *Config) Secret() cmn.Censored { return cmn.Censored(*c.Server.psecret) }
85-
func (c *Config) Expire() time.Duration { return time.Duration(*c.Server.pexpire) }
89+
func (c *Config) SetPrivateKey(key *rsa.PrivateKey) { c.Server.pKey = key }
90+
func (c *Config) GetPrivateKey() *rsa.PrivateKey { return c.Server.pKey }
91+
func (c *Config) Secret() cmn.Censored { return cmn.Censored(*c.Server.psecret) }
92+
func (c *Config) Expire() time.Duration { return time.Duration(*c.Server.pexpire) }

api/authn/oidc.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Package authn provides AuthN API over HTTP(S)
2+
/*
3+
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
4+
*/
5+
package authn
6+
7+
import (
8+
"net/url"
9+
10+
"github.com/NVIDIA/aistore/api/apc"
11+
)
12+
13+
const (
14+
SigningMethodHS256 = "HS256"
15+
SigningMethodRS256 = "RS256"
16+
)
17+
18+
// OIDCConfiguration -- Partial implementation of OIDC spec: https://openid.net/specs/openid-connect-discovery-1_0.html
19+
type OIDCConfiguration struct {
20+
Issuer string `json:"issuer"`
21+
TokenEndpoint string `json:"token_endpoint"`
22+
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
23+
JWKSURI string `json:"jwks_uri"`
24+
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
25+
}
26+
27+
func NewOIDCConfiguration(base *url.URL) *OIDCConfiguration {
28+
return &OIDCConfiguration{
29+
Issuer: base.String(),
30+
TokenEndpoint: base.JoinPath(apc.URLPathTokens.S).String(),
31+
UserinfoEndpoint: base.JoinPath(apc.URLPathUsers.S).String(),
32+
JWKSURI: base.JoinPath(apc.URLPathJWKS.S).String(),
33+
IDTokenSigningAlgValuesSupported: []string{SigningMethodHS256, SigningMethodRS256},
34+
}
35+
}

api/env/authn.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
AisAuthConfDir = "AIS_AUTHN_CONF_DIR" // contains AuthN config and tokens DB
1717
AisAuthLogDir = "AIS_AUTHN_LOG_DIR"
1818
AisAuthLogLevel = "AIS_AUTHN_LOG_LEVEL"
19+
AisAuthExternalURL = "AIS_AUTHN_EXTERNAL_URL"
1920
AisAuthPort = "AIS_AUTHN_PORT"
2021
AisAuthTTL = "AIS_AUTHN_TTL"
2122
AisAuthUseHTTPS = "AIS_AUTHN_USE_HTTPS"

cmd/authn/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Authentication Server (AuthN) provides a token-based secure access to AIStore.
22
It employs [JSON Web Tokens](https://github.com/golang-jwt/jwt) framework to grant access to resources:
33
buckets and objects. Please read a short [introduction to JWT](https://jwt.io/introduction/) for details.
4-
Currently, we only support hash-based message authentication (HMAC) using SHA256 hash.
4+
5+
Currently supported token signing methods:
6+
- Symmetric hash-based message authentication (HMAC) using SHA256 hash.
7+
- Asymmetric RSA-256 keys
8+
59

610
Further details at [AuthN documentation](/docs/authn.md).

cmd/authn/aisreq.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,21 @@ const (
3030

3131
// Send request to the defined cluster to validate that the cluster will allow tokens issued by this AuthN service
3232
func (m *mgr) validateCluster(clu *authn.CluACL) (err error) {
33-
if m.cm.HasHMACSecret() {
34-
return m.validateSecret(clu)
33+
var (
34+
tag string
35+
body []byte
36+
)
37+
switch {
38+
case m.cm.HasHMACSecret():
39+
tag = "validate-secret"
40+
body = cos.MustMarshal(&authn.ServerConf{Secret: m.cm.GetSecretChecksum()})
41+
case m.cm.GetPublicKeyString() != nil:
42+
tag = "validate-key"
43+
body = cos.MustMarshal(&authn.ServerConf{PubKey: m.cm.GetPublicKeyString()})
44+
default:
45+
return errors.New("invalid cluster configuration, no signing key configured")
3546
}
36-
return errors.New("invalid cluster configuration, no signing key configured")
37-
}
3847

39-
func (m *mgr) validateSecret(clu *authn.CluACL) (err error) {
40-
const tag = "validate-secret"
41-
body := cos.MustMarshal(&authn.ServerConf{Secret: m.cm.GetSecretChecksum()})
4248
for _, u := range clu.URLs {
4349
if err = m.call(http.MethodPost, u, apc.Tokens, body, tag); err == nil {
4450
return

0 commit comments

Comments
 (0)