Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
33046de3
Commit
33046de3
authored
Jan 23, 2018
by
Mike Danese
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
move service account signing to using go-jose
also extract custom validation to seperate function
parent
42cc2f36
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
127 additions
and
144 deletions
+127
-144
BUILD
pkg/serviceaccount/BUILD
+3
-1
jwt.go
pkg/serviceaccount/jwt.go
+124
-143
No files found.
pkg/serviceaccount/BUILD
View file @
33046de3
...
@@ -15,9 +15,11 @@ go_library(
...
@@ -15,9 +15,11 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/serviceaccount",
importpath = "k8s.io/kubernetes/pkg/serviceaccount",
deps = [
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/core:go_default_library",
"//vendor/github.com/dgrijalva/jwt-go:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2/jwt:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
...
...
pkg/serviceaccount/jwt.go
View file @
33046de3
...
@@ -25,24 +25,24 @@ import (
...
@@ -25,24 +25,24 @@ import (
"fmt"
"fmt"
"k8s.io/api/core/v1"
"k8s.io/api/core/v1"
utilerrors
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/authenticator"
apiserverserviceaccount
"k8s.io/apiserver/pkg/authentication/serviceaccount"
apiserverserviceaccount
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authentication/user"
jwt
"github.com/dgrijalva/jwt-go"
"github.com/golang/glog"
"github.com/golang/glog"
jose
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
)
const
(
const
Issuer
=
"kubernetes/serviceaccount"
Issuer
=
"kubernetes/serviceaccount"
SubjectClaim
=
"sub"
type
privateClaims
struct
{
IssuerClaim
=
"iss"
ServiceAccountName
string
`json:"kubernetes.io/serviceaccount/service-account.name"`
ServiceAccountNameClaim
=
"kubernetes.io/serviceaccount/service-account.name"
ServiceAccountUID
string
`json:"kubernetes.io/serviceaccount/service-account.uid"`
ServiceAccountUIDClaim
=
"kubernetes.io/serviceaccount/service-account.uid"
SecretName
string
`json:"kubernetes.io/serviceaccount/secret.name"`
SecretNameClaim
=
"kubernetes.io/serviceaccount/secret.name"
Namespace
string
`json:"kubernetes.io/serviceaccount/namespace"`
NamespaceClaim
=
"kubernetes.io/serviceaccount/namespace"
}
)
// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
type
ServiceAccountTokenGetter
interface
{
type
ServiceAccountTokenGetter
interface
{
...
@@ -68,18 +68,18 @@ type jwtTokenGenerator struct {
...
@@ -68,18 +68,18 @@ type jwtTokenGenerator struct {
}
}
func
(
j
*
jwtTokenGenerator
)
GenerateToken
(
serviceAccount
v1
.
ServiceAccount
,
secret
v1
.
Secret
)
(
string
,
error
)
{
func
(
j
*
jwtTokenGenerator
)
GenerateToken
(
serviceAccount
v1
.
ServiceAccount
,
secret
v1
.
Secret
)
(
string
,
error
)
{
var
method
jwt
.
SigningMethod
var
alg
jose
.
SignatureAlgorithm
switch
privateKey
:=
j
.
privateKey
.
(
type
)
{
switch
privateKey
:=
j
.
privateKey
.
(
type
)
{
case
*
rsa
.
PrivateKey
:
case
*
rsa
.
PrivateKey
:
method
=
jwt
.
SigningMethod
RS256
alg
=
jose
.
RS256
case
*
ecdsa
.
PrivateKey
:
case
*
ecdsa
.
PrivateKey
:
switch
privateKey
.
Curve
{
switch
privateKey
.
Curve
{
case
elliptic
.
P256
()
:
case
elliptic
.
P256
()
:
method
=
jwt
.
SigningMethod
ES256
alg
=
jose
.
ES256
case
elliptic
.
P384
()
:
case
elliptic
.
P384
()
:
method
=
jwt
.
SigningMethod
ES384
alg
=
jose
.
ES384
case
elliptic
.
P521
()
:
case
elliptic
.
P521
()
:
method
=
jwt
.
SigningMethod
ES512
alg
=
jose
.
ES512
default
:
default
:
return
""
,
fmt
.
Errorf
(
"unknown private key curve, must be 256, 384, or 521"
)
return
""
,
fmt
.
Errorf
(
"unknown private key curve, must be 256, 384, or 521"
)
}
}
...
@@ -87,24 +87,28 @@ func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secr
...
@@ -87,24 +87,28 @@ func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secr
return
""
,
fmt
.
Errorf
(
"unknown private key type %T, must be *rsa.PrivateKey or *ecdsa.PrivateKey"
,
j
.
privateKey
)
return
""
,
fmt
.
Errorf
(
"unknown private key type %T, must be *rsa.PrivateKey or *ecdsa.PrivateKey"
,
j
.
privateKey
)
}
}
token
:=
jwt
.
New
(
method
)
signer
,
err
:=
jose
.
NewSigner
(
jose
.
SigningKey
{
claims
,
_
:=
token
.
Claims
.
(
jwt
.
MapClaims
)
Algorithm
:
alg
,
Key
:
j
.
privateKey
,
// Identify the issuer
},
claims
[
IssuerClaim
]
=
Issuer
nil
,
)
// Username
if
err
!=
nil
{
claims
[
SubjectClaim
]
=
apiserverserviceaccount
.
MakeUsername
(
serviceAccount
.
Namespace
,
serviceAccount
.
Name
)
return
""
,
err
}
// Persist enough structured info for the authenticator to be able to look up the service account and secret
claims
[
NamespaceClaim
]
=
serviceAccount
.
Namespace
claims
[
ServiceAccountNameClaim
]
=
serviceAccount
.
Name
claims
[
ServiceAccountUIDClaim
]
=
serviceAccount
.
UID
claims
[
SecretNameClaim
]
=
secret
.
Name
// Sign and get the complete encoded token as a string
return
jwt
.
Signed
(
signer
)
.
return
token
.
SignedString
(
j
.
privateKey
)
Claims
(
&
jwt
.
Claims
{
Issuer
:
Issuer
,
Subject
:
apiserverserviceaccount
.
MakeUsername
(
serviceAccount
.
Namespace
,
serviceAccount
.
Name
),
})
.
Claims
(
&
privateClaims
{
Namespace
:
serviceAccount
.
Namespace
,
ServiceAccountName
:
serviceAccount
.
Name
,
ServiceAccountUID
:
string
(
serviceAccount
.
UID
),
SecretName
:
secret
.
Name
,
})
.
CompactSerialize
()
}
}
// JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator
// JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator
...
@@ -122,129 +126,106 @@ type jwtTokenAuthenticator struct {
...
@@ -122,129 +126,106 @@ type jwtTokenAuthenticator struct {
var
errMismatchedSigningMethod
=
errors
.
New
(
"invalid signing method"
)
var
errMismatchedSigningMethod
=
errors
.
New
(
"invalid signing method"
)
func
(
j
*
jwtTokenAuthenticator
)
AuthenticateToken
(
token
string
)
(
user
.
Info
,
bool
,
error
)
{
func
(
j
*
jwtTokenAuthenticator
)
AuthenticateToken
(
tokenData
string
)
(
user
.
Info
,
bool
,
error
)
{
var
validationError
error
tok
,
err
:=
jwt
.
ParseSigned
(
tokenData
)
if
err
!=
nil
{
for
i
,
key
:=
range
j
.
keys
{
return
nil
,
false
,
nil
// Attempt to verify with each key until we find one that works
}
parsedToken
,
err
:=
jwt
.
Parse
(
token
,
func
(
token
*
jwt
.
Token
)
(
interface
{},
error
)
{
switch
token
.
Method
.
(
type
)
{
case
*
jwt
.
SigningMethodRSA
:
if
_
,
ok
:=
key
.
(
*
rsa
.
PublicKey
);
ok
{
return
key
,
nil
}
return
nil
,
errMismatchedSigningMethod
case
*
jwt
.
SigningMethodECDSA
:
if
_
,
ok
:=
key
.
(
*
ecdsa
.
PublicKey
);
ok
{
return
key
,
nil
}
return
nil
,
errMismatchedSigningMethod
default
:
return
nil
,
fmt
.
Errorf
(
"Unexpected signing method: %v"
,
token
.
Header
[
"alg"
])
}
})
if
err
!=
nil
{
public
:=
&
jwt
.
Claims
{}
switch
err
:=
err
.
(
type
)
{
private
:=
&
privateClaims
{}
case
*
jwt
.
ValidationError
:
if
(
err
.
Errors
&
jwt
.
ValidationErrorMalformed
)
!=
0
{
var
(
// Not a JWT, no point in continuing
found
bool
return
nil
,
false
,
nil
errlist
[]
error
}
)
for
_
,
key
:=
range
j
.
keys
{
if
(
err
.
Errors
&
jwt
.
ValidationErrorSignatureInvalid
)
!=
0
{
if
err
:=
tok
.
Claims
(
key
,
public
,
private
);
err
!=
nil
{
// Signature error, perhaps one of the other keys will verify the signature
errlist
=
append
(
errlist
,
err
)
// If not, we want to return this error
continue
glog
.
V
(
4
)
.
Infof
(
"Signature error (key %d): %v"
,
i
,
err
)
validationError
=
err
continue
}
// This key doesn't apply to the given signature type
// Perhaps one of the other keys will verify the signature
// If not, we want to return this error
if
err
.
Inner
==
errMismatchedSigningMethod
{
glog
.
V
(
4
)
.
Infof
(
"Mismatched key type (key %d): %v"
,
i
,
err
)
validationError
=
err
continue
}
}
// Other errors should just return as errors
return
nil
,
false
,
err
}
}
found
=
true
break
}
// If we get here, we have a token with a recognized signature
if
!
found
{
return
nil
,
false
,
utilerrors
.
NewAggregate
(
errlist
)
}
claims
,
_
:=
parsedToken
.
Claims
.
(
jwt
.
MapClaims
)
// If we get here, we have a token with a recognized signature
// Make sure we issued the token
// Make sure we issued the token
iss
,
_
:=
claims
[
IssuerClaim
]
.
(
string
)
if
public
.
Issuer
!=
Issuer
{
if
iss
!=
Issuer
{
return
nil
,
false
,
nil
return
nil
,
false
,
nil
}
}
// Make sure the claims we need exist
if
err
:=
j
.
Validate
(
tokenData
,
public
,
private
);
err
!=
nil
{
sub
,
_
:=
claims
[
SubjectClaim
]
.
(
string
)
return
nil
,
false
,
err
if
len
(
sub
)
==
0
{
}
return
nil
,
false
,
errors
.
New
(
"sub claim is missing"
)
}
return
UserInfo
(
private
.
Namespace
,
private
.
ServiceAccountName
,
private
.
ServiceAccountUID
),
true
,
nil
namespace
,
_
:=
claims
[
NamespaceClaim
]
.
(
string
)
if
len
(
namespace
)
==
0
{
}
return
nil
,
false
,
errors
.
New
(
"namespace claim is missing"
)
}
func
(
j
*
jwtTokenAuthenticator
)
Validate
(
tokenData
string
,
public
*
jwt
.
Claims
,
private
*
privateClaims
)
error
{
secretName
,
_
:=
claims
[
SecretNameClaim
]
.
(
string
)
if
len
(
secretName
)
==
0
{
// Make sure the claims we need exist
return
nil
,
false
,
errors
.
New
(
"secretName claim is missing"
)
if
len
(
public
.
Subject
)
==
0
{
return
errors
.
New
(
"sub claim is missing"
)
}
namespace
:=
private
.
Namespace
if
len
(
namespace
)
==
0
{
return
errors
.
New
(
"namespace claim is missing"
)
}
secretName
:=
private
.
SecretName
if
len
(
secretName
)
==
0
{
return
errors
.
New
(
"secretName claim is missing"
)
}
serviceAccountName
:=
private
.
ServiceAccountName
if
len
(
serviceAccountName
)
==
0
{
return
errors
.
New
(
"serviceAccountName claim is missing"
)
}
serviceAccountUID
:=
private
.
ServiceAccountUID
if
len
(
serviceAccountUID
)
==
0
{
return
errors
.
New
(
"serviceAccountUID claim is missing"
)
}
subjectNamespace
,
subjectName
,
err
:=
apiserverserviceaccount
.
SplitUsername
(
public
.
Subject
)
if
err
!=
nil
||
subjectNamespace
!=
namespace
||
subjectName
!=
serviceAccountName
{
return
errors
.
New
(
"sub claim is invalid"
)
}
if
j
.
lookup
{
// Make sure token hasn't been invalidated by deletion of the secret
secret
,
err
:=
j
.
getter
.
GetSecret
(
namespace
,
secretName
)
if
err
!=
nil
{
glog
.
V
(
4
)
.
Infof
(
"Could not retrieve token %s/%s for service account %s/%s: %v"
,
namespace
,
secretName
,
namespace
,
serviceAccountName
,
err
)
return
errors
.
New
(
"Token has been invalidated"
)
}
}
serviceAccountName
,
_
:=
claims
[
ServiceAccountNameClaim
]
.
(
string
)
if
secret
.
DeletionTimestamp
!=
nil
{
if
len
(
serviceAccountName
)
==
0
{
glog
.
V
(
4
)
.
Infof
(
"Token is deleted and awaiting removal: %s/%s for service account %s/%s"
,
namespace
,
secretName
,
namespace
,
serviceAccountName
)
return
nil
,
false
,
errors
.
New
(
"serviceAccountName claim is missing
"
)
return
errors
.
New
(
"Token has been invalidated
"
)
}
}
serviceAccountUID
,
_
:=
claims
[
ServiceAccountUIDClaim
]
.
(
string
)
if
bytes
.
Compare
(
secret
.
Data
[
v1
.
ServiceAccountTokenKey
],
[]
byte
(
tokenData
))
!=
0
{
if
len
(
serviceAccountUID
)
==
0
{
glog
.
V
(
4
)
.
Infof
(
"Token contents no longer matches %s/%s for service account %s/%s"
,
namespace
,
secretName
,
namespace
,
serviceAccountName
)
return
nil
,
false
,
errors
.
New
(
"serviceAccountUID claim is missing
"
)
return
errors
.
New
(
"Token does not match server's copy
"
)
}
}
subjectNamespace
,
subjectName
,
err
:=
apiserverserviceaccount
.
SplitUsername
(
sub
)
// Make sure service account still exists (name and UID)
if
err
!=
nil
||
subjectNamespace
!=
namespace
||
subjectName
!=
serviceAccountName
{
serviceAccount
,
err
:=
j
.
getter
.
GetServiceAccount
(
namespace
,
serviceAccountName
)
return
nil
,
false
,
errors
.
New
(
"sub claim is invalid"
)
if
err
!=
nil
{
glog
.
V
(
4
)
.
Infof
(
"Could not retrieve service account %s/%s: %v"
,
namespace
,
serviceAccountName
,
err
)
return
err
}
}
if
serviceAccount
.
DeletionTimestamp
!=
nil
{
if
j
.
lookup
{
glog
.
V
(
4
)
.
Infof
(
"Service account has been deleted %s/%s"
,
namespace
,
serviceAccountName
)
// Make sure token hasn't been invalidated by deletion of the secret
return
fmt
.
Errorf
(
"ServiceAccount %s/%s has been deleted"
,
namespace
,
serviceAccountName
)
secret
,
err
:=
j
.
getter
.
GetSecret
(
namespace
,
secretName
)
}
if
err
!=
nil
{
if
string
(
serviceAccount
.
UID
)
!=
serviceAccountUID
{
glog
.
V
(
4
)
.
Infof
(
"Could not retrieve token %s/%s for service account %s/%s: %v"
,
namespace
,
secretName
,
namespace
,
serviceAccountName
,
err
)
glog
.
V
(
4
)
.
Infof
(
"Service account UID no longer matches %s/%s: %q != %q"
,
namespace
,
serviceAccountName
,
string
(
serviceAccount
.
UID
),
serviceAccountUID
)
return
nil
,
false
,
errors
.
New
(
"Token has been invalidated"
)
return
fmt
.
Errorf
(
"ServiceAccount UID (%s) does not match claim (%s)"
,
serviceAccount
.
UID
,
serviceAccountUID
)
}
if
secret
.
DeletionTimestamp
!=
nil
{
glog
.
V
(
4
)
.
Infof
(
"Token is deleted and awaiting removal: %s/%s for service account %s/%s"
,
namespace
,
secretName
,
namespace
,
serviceAccountName
)
return
nil
,
false
,
errors
.
New
(
"Token has been invalidated"
)
}
if
bytes
.
Compare
(
secret
.
Data
[
v1
.
ServiceAccountTokenKey
],
[]
byte
(
token
))
!=
0
{
glog
.
V
(
4
)
.
Infof
(
"Token contents no longer matches %s/%s for service account %s/%s"
,
namespace
,
secretName
,
namespace
,
serviceAccountName
)
return
nil
,
false
,
errors
.
New
(
"Token does not match server's copy"
)
}
// Make sure service account still exists (name and UID)
serviceAccount
,
err
:=
j
.
getter
.
GetServiceAccount
(
namespace
,
serviceAccountName
)
if
err
!=
nil
{
glog
.
V
(
4
)
.
Infof
(
"Could not retrieve service account %s/%s: %v"
,
namespace
,
serviceAccountName
,
err
)
return
nil
,
false
,
err
}
if
serviceAccount
.
DeletionTimestamp
!=
nil
{
glog
.
V
(
4
)
.
Infof
(
"Service account has been deleted %s/%s"
,
namespace
,
serviceAccountName
)
return
nil
,
false
,
fmt
.
Errorf
(
"ServiceAccount %s/%s has been deleted"
,
namespace
,
serviceAccountName
)
}
if
string
(
serviceAccount
.
UID
)
!=
serviceAccountUID
{
glog
.
V
(
4
)
.
Infof
(
"Service account UID no longer matches %s/%s: %q != %q"
,
namespace
,
serviceAccountName
,
string
(
serviceAccount
.
UID
),
serviceAccountUID
)
return
nil
,
false
,
fmt
.
Errorf
(
"ServiceAccount UID (%s) does not match claim (%s)"
,
serviceAccount
.
UID
,
serviceAccountUID
)
}
}
}
return
UserInfo
(
namespace
,
serviceAccountName
,
serviceAccountUID
),
true
,
nil
}
}
return
nil
,
false
,
validationError
return
nil
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment