256 lines
8.3 KiB
Go
256 lines
8.3 KiB
Go
/*
|
|
* Copyright 2022-2024 The NATS Authors
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package jwt
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/nats-io/nkeys"
|
|
)
|
|
|
|
// ServerID is basic static info for a NATS server.
|
|
type ServerID struct {
|
|
Name string `json:"name"`
|
|
Host string `json:"host"`
|
|
ID string `json:"id"`
|
|
Version string `json:"version,omitempty"`
|
|
Cluster string `json:"cluster,omitempty"`
|
|
Tags TagList `json:"tags,omitempty"`
|
|
XKey string `json:"xkey,omitempty"`
|
|
}
|
|
|
|
// ClientInformation is information about a client that is trying to authorize.
|
|
type ClientInformation struct {
|
|
Host string `json:"host,omitempty"`
|
|
ID uint64 `json:"id,omitempty"`
|
|
User string `json:"user,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Tags TagList `json:"tags,omitempty"`
|
|
NameTag string `json:"name_tag,omitempty"`
|
|
Kind string `json:"kind,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
MQTT string `json:"mqtt_id,omitempty"`
|
|
Nonce string `json:"nonce,omitempty"`
|
|
}
|
|
|
|
// ConnectOptions represents options that were set in the CONNECT protocol from the client
|
|
// during authorization.
|
|
type ConnectOptions struct {
|
|
JWT string `json:"jwt,omitempty"`
|
|
Nkey string `json:"nkey,omitempty"`
|
|
SignedNonce string `json:"sig,omitempty"`
|
|
Token string `json:"auth_token,omitempty"`
|
|
Username string `json:"user,omitempty"`
|
|
Password string `json:"pass,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Lang string `json:"lang,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
Protocol int `json:"protocol"`
|
|
}
|
|
|
|
// ClientTLS is information about TLS state if present, including client certs.
|
|
// If the client certs were present and verified they will be under verified chains
|
|
// with the client peer cert being VerifiedChains[0]. These are complete and pem encoded.
|
|
// If they were not verified, they will be under certs.
|
|
type ClientTLS struct {
|
|
Version string `json:"version,omitempty"`
|
|
Cipher string `json:"cipher,omitempty"`
|
|
Certs StringList `json:"certs,omitempty"`
|
|
VerifiedChains []StringList `json:"verified_chains,omitempty"`
|
|
}
|
|
|
|
// AuthorizationRequest represents all the information we know about the client that
|
|
// will be sent to an external authorization service.
|
|
type AuthorizationRequest struct {
|
|
Server ServerID `json:"server_id"`
|
|
UserNkey string `json:"user_nkey"`
|
|
ClientInformation ClientInformation `json:"client_info"`
|
|
ConnectOptions ConnectOptions `json:"connect_opts"`
|
|
TLS *ClientTLS `json:"client_tls,omitempty"`
|
|
RequestNonce string `json:"request_nonce,omitempty"`
|
|
GenericFields
|
|
}
|
|
|
|
// AuthorizationRequestClaims defines an external auth request JWT.
|
|
// These wil be signed by a NATS server.
|
|
type AuthorizationRequestClaims struct {
|
|
ClaimsData
|
|
AuthorizationRequest `json:"nats"`
|
|
}
|
|
|
|
// NewAuthorizationRequestClaims creates an auth request JWT with the specific subject/public key.
|
|
func NewAuthorizationRequestClaims(subject string) *AuthorizationRequestClaims {
|
|
if subject == "" {
|
|
return nil
|
|
}
|
|
var ac AuthorizationRequestClaims
|
|
ac.Subject = subject
|
|
return &ac
|
|
}
|
|
|
|
// Validate checks the generic and specific parts of the auth request jwt.
|
|
func (ac *AuthorizationRequestClaims) Validate(vr *ValidationResults) {
|
|
if ac.UserNkey == "" {
|
|
vr.AddError("User nkey is required")
|
|
} else if !nkeys.IsValidPublicUserKey(ac.UserNkey) {
|
|
vr.AddError("User nkey %q is not a valid user public key", ac.UserNkey)
|
|
}
|
|
ac.ClaimsData.Validate(vr)
|
|
}
|
|
|
|
// Encode tries to turn the auth request claims into a JWT string.
|
|
func (ac *AuthorizationRequestClaims) Encode(pair nkeys.KeyPair) (string, error) {
|
|
return ac.EncodeWithSigner(pair, nil)
|
|
}
|
|
|
|
func (ac *AuthorizationRequestClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) {
|
|
ac.Type = AuthorizationRequestClaim
|
|
return ac.ClaimsData.encode(pair, ac, fn)
|
|
}
|
|
|
|
// DecodeAuthorizationRequestClaims tries to parse an auth request claims from a JWT string
|
|
func DecodeAuthorizationRequestClaims(token string) (*AuthorizationRequestClaims, error) {
|
|
claims, err := Decode(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ac, ok := claims.(*AuthorizationRequestClaims)
|
|
if !ok {
|
|
return nil, errors.New("not an authorization request claim")
|
|
}
|
|
return ac, nil
|
|
}
|
|
|
|
// ExpectedPrefixes defines the types that can encode an auth request jwt, servers.
|
|
func (ac *AuthorizationRequestClaims) ExpectedPrefixes() []nkeys.PrefixByte {
|
|
return []nkeys.PrefixByte{nkeys.PrefixByteServer}
|
|
}
|
|
|
|
func (ac *AuthorizationRequestClaims) ClaimType() ClaimType {
|
|
return ac.Type
|
|
}
|
|
|
|
// Claims returns the request claims data.
|
|
func (ac *AuthorizationRequestClaims) Claims() *ClaimsData {
|
|
return &ac.ClaimsData
|
|
}
|
|
|
|
// Payload pulls the request specific payload out of the claims.
|
|
func (ac *AuthorizationRequestClaims) Payload() interface{} {
|
|
return &ac.AuthorizationRequest
|
|
}
|
|
|
|
func (ac *AuthorizationRequestClaims) String() string {
|
|
return ac.ClaimsData.String(ac)
|
|
}
|
|
|
|
func (ac *AuthorizationRequestClaims) updateVersion() {
|
|
ac.GenericFields.Version = libVersion
|
|
}
|
|
|
|
type AuthorizationResponse struct {
|
|
Jwt string `json:"jwt,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
// IssuerAccount stores the public key for the account the issuer represents.
|
|
// When set, the claim was issued by a signing key.
|
|
IssuerAccount string `json:"issuer_account,omitempty"`
|
|
GenericFields
|
|
}
|
|
|
|
type AuthorizationResponseClaims struct {
|
|
ClaimsData
|
|
AuthorizationResponse `json:"nats"`
|
|
}
|
|
|
|
func NewAuthorizationResponseClaims(subject string) *AuthorizationResponseClaims {
|
|
if subject == "" {
|
|
return nil
|
|
}
|
|
var ac AuthorizationResponseClaims
|
|
ac.Subject = subject
|
|
return &ac
|
|
}
|
|
|
|
// DecodeAuthorizationResponseClaims tries to parse an auth request claims from a JWT string
|
|
func DecodeAuthorizationResponseClaims(token string) (*AuthorizationResponseClaims, error) {
|
|
claims, err := Decode(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ac, ok := claims.(*AuthorizationResponseClaims)
|
|
if !ok {
|
|
return nil, errors.New("not an authorization request claim")
|
|
}
|
|
return ac, nil
|
|
}
|
|
|
|
// ExpectedPrefixes defines the types that can encode an auth request jwt, servers.
|
|
func (ar *AuthorizationResponseClaims) ExpectedPrefixes() []nkeys.PrefixByte {
|
|
return []nkeys.PrefixByte{nkeys.PrefixByteAccount}
|
|
}
|
|
|
|
func (ar *AuthorizationResponseClaims) ClaimType() ClaimType {
|
|
return ar.Type
|
|
}
|
|
|
|
// Claims returns the request claims data.
|
|
func (ar *AuthorizationResponseClaims) Claims() *ClaimsData {
|
|
return &ar.ClaimsData
|
|
}
|
|
|
|
// Payload pulls the request specific payload out of the claims.
|
|
func (ar *AuthorizationResponseClaims) Payload() interface{} {
|
|
return &ar.AuthorizationResponse
|
|
}
|
|
|
|
func (ar *AuthorizationResponseClaims) String() string {
|
|
return ar.ClaimsData.String(ar)
|
|
}
|
|
|
|
func (ar *AuthorizationResponseClaims) updateVersion() {
|
|
ar.GenericFields.Version = libVersion
|
|
}
|
|
|
|
// Validate checks the generic and specific parts of the auth request jwt.
|
|
func (ar *AuthorizationResponseClaims) Validate(vr *ValidationResults) {
|
|
if !nkeys.IsValidPublicUserKey(ar.Subject) {
|
|
vr.AddError("Subject must be a user public key")
|
|
}
|
|
if !nkeys.IsValidPublicServerKey(ar.Audience) {
|
|
vr.AddError("Audience must be a server public key")
|
|
}
|
|
if ar.Error == "" && ar.Jwt == "" {
|
|
vr.AddError("Error or Jwt is required")
|
|
}
|
|
if ar.Error != "" && ar.Jwt != "" {
|
|
vr.AddError("Only Error or Jwt can be set")
|
|
}
|
|
if ar.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(ar.IssuerAccount) {
|
|
vr.AddError("issuer_account is not an account public key")
|
|
}
|
|
ar.ClaimsData.Validate(vr)
|
|
}
|
|
|
|
// Encode tries to turn the auth request claims into a JWT string.
|
|
func (ar *AuthorizationResponseClaims) Encode(pair nkeys.KeyPair) (string, error) {
|
|
return ar.EncodeWithSigner(pair, nil)
|
|
}
|
|
|
|
func (ar *AuthorizationResponseClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) {
|
|
ar.Type = AuthorizationResponseClaim
|
|
return ar.ClaimsData.encode(pair, ar, fn)
|
|
}
|