1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
package token
import (
"encoding/json"
"regexp"
"strings"
"time"
"github.com/pkg/errors"
"github.com/smallstep/cli/jose"
)
// Type indicates the token Type.
type Type int
// Token types supported.
const (
Unknown Type = iota
JWK // Smallstep JWK
X5C // Smallstep JWK with x5c header
OIDC // OpenID Connect
GCP // Google Cloud Platform
AWS // Amazon Web Services
Azure // Microsoft Azure
K8sSA // Kubernetes Service Account
)
// JSONWebToken represents a JSON Web Token (as specified in RFC7519). Using the
// Parse or ParseInsecure it will contain the payloads supported on step ca.
type JSONWebToken struct {
*jose.JSONWebToken
Payload Payload
}
// Payload represents public claim values (as specified in RFC 7519). In
// addition to the standard claims it contains the ones supported in step ca.
type Payload struct {
jose.Claims
SHA string `json:"sha"` // JWK token claims
SANs []string `json:"sans"` // ...
AtHash string `json:"at_hash"` // OIDC token claims
AuthorizedParty string `json:"azp"` // ...
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
Hd string `json:"hd"`
Nonce string `json:"nonce"`
AppID string `json:"appid"` // Azure token claims
AppIDAcr string `json:"appidacr"` // ...
IdentityProvider string `json:"idp"`
ObjectID string `json:"oid"`
TenantID string `json:"tid"`
Version interface{} `json:"ver"`
XMSMirID string `json:"xms_mirid"`
K8sSANamespace string `json:"kubernetes.io/serviceaccount/namespace,omitempty"`
K8sSASecretName string `json:"kubernetes.io/serviceaccount/secret.name,omitempty"`
K8sSAServiceAccountName string `json:"kubernetes.io/serviceaccount/service-account.name,omitempty"`
K8sSAServiceAccountUID string `json:"kubernetes.io/serviceaccount/service-account.uid,omitempty"`
Google *GCPGooglePayload `json:"google"` // GCP token claims
Amazon *AWSAmazonPayload `json:"amazon"` // AWS token claims
Azure *AzurePayload `json:"azure"` // Azure token claims
}
// Type returns the type of the payload.
func (p Payload) Type() Type {
switch {
case p.Google != nil:
return GCP
case p.Amazon != nil:
return AWS
case p.Azure != nil:
return Azure
case p.Issuer == "kubernetes/serviceaccount":
return K8sSA
case len(p.SHA) > 0 || len(p.SANs) > 0:
return JWK
case p.Email != "":
return OIDC
default:
return Unknown
}
}
// GCPGooglePayload represents the Google payload in GCP.
type GCPGooglePayload struct {
ComputeEngine GCPComputeEnginePayload `json:"compute_engine"`
}
// GCPComputeEnginePayload represents the Google ComputeEngine payload in GCP.
type GCPComputeEnginePayload struct {
InstanceID string `json:"instance_id"`
InstanceName string `json:"instance_name"`
InstanceCreationTimestamp *jose.NumericDate `json:"instance_creation_timestamp"`
ProjectID string `json:"project_id"`
ProjectNumber int64 `json:"project_number"`
Zone string `json:"zone"`
LicenseID []string `json:"license_id"`
}
// AWSAmazonPayload represents the Amazon payload for a AWS token.
type AWSAmazonPayload struct {
Document []byte `json:"document"`
Signature []byte `json:"signature"`
InstanceIdentityDocument *AWSInstanceIdentityDocument `json:"-"`
}
// AWSInstanceIdentityDocument is the JSON representation of the instance
// identity document.
type AWSInstanceIdentityDocument struct {
AccountID string `json:"accountId"`
Architecture string `json:"architecture"`
AvailabilityZone string `json:"availabilityZone"`
BillingProducts []string `json:"billingProducts"`
DevpayProductCodes []string `json:"devpayProductCodes"`
ImageID string `json:"imageId"`
InstanceID string `json:"instanceId"`
InstanceType string `json:"instanceType"`
KernelID string `json:"kernelId"`
PendingTime time.Time `json:"pendingTime"`
PrivateIP string `json:"privateIp"`
RamdiskID string `json:"ramdiskId"`
Region string `json:"region"`
Version string `json:"version"`
}
// azureXMSMirIDRegExp is the regular expression used to parse the xms_mirid claim.
// Using case insensitive as resourceGroups appears as resourcegroups.
var azureXMSMirIDRegExp = regexp.MustCompile(`(?i)^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`)
// AzurePayload contains the information in the xms_mirid claim.
type AzurePayload struct {
SubscriptionID string
ResourceGroup string
VirtualMachine string
}
// Parse parses the given token verifying the signature with the key.
func Parse(token string, key interface{}) (*JSONWebToken, error) {
jwt, err := jose.ParseSigned(token)
if err != nil {
return nil, errors.Wrap(err, "error parsing token")
}
var p Payload
if err := jwt.Claims(key, &p); err != nil {
return nil, errors.Wrap(err, "error parsing token claims")
}
return parseResponse(jwt, p)
}
// ParseInsecure parses the given token.
func ParseInsecure(token string) (*JSONWebToken, error) {
jwt, err := jose.ParseSigned(token)
if err != nil {
return nil, errors.Wrap(err, "error parsing token")
}
var p Payload
if err := jwt.UnsafeClaimsWithoutVerification(&p); err != nil {
return nil, errors.Wrap(err, "error parsing token claims")
}
return parseResponse(jwt, p)
}
func parseResponse(jwt *jose.JSONWebToken, p Payload) (*JSONWebToken, error) {
switch {
case p.Type() == AWS:
if err := json.Unmarshal(p.Amazon.Document, &p.Amazon.InstanceIdentityDocument); err != nil {
return nil, errors.Wrap(err, "error unmarshaling instance identity document")
}
case strings.HasPrefix(p.Issuer, "https://sts.windows.net/"):
if re := azureXMSMirIDRegExp.FindStringSubmatch(p.XMSMirID); len(re) > 0 {
p.Azure = &AzurePayload{
SubscriptionID: re[1],
ResourceGroup: re[2],
VirtualMachine: re[3],
}
}
}
return &JSONWebToken{
JSONWebToken: jwt,
Payload: p,
}, nil
}
|