Unverified Commit f38cc955 authored by k8s-ci-robot's avatar k8s-ci-robot Committed by GitHub

Merge pull request #62692 from mikedanese/trev2

authn: extend authenticator.Token to support audience validation
parents 39c82199 effad15e
...@@ -80871,6 +80871,13 @@ ...@@ -80871,6 +80871,13 @@
"io.k8s.api.authentication.v1.TokenReviewSpec": { "io.k8s.api.authentication.v1.TokenReviewSpec": {
"description": "TokenReviewSpec is a description of the token authentication request.", "description": "TokenReviewSpec is a description of the token authentication request.",
"properties": { "properties": {
"audiences": {
"description": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.",
"type": "array",
"items": {
"type": "string"
}
},
"token": { "token": {
"description": "Token is the opaque bearer token.", "description": "Token is the opaque bearer token.",
"type": "string" "type": "string"
...@@ -80880,6 +80887,13 @@ ...@@ -80880,6 +80887,13 @@
"io.k8s.api.authentication.v1.TokenReviewStatus": { "io.k8s.api.authentication.v1.TokenReviewStatus": {
"description": "TokenReviewStatus is the result of the token authentication request.", "description": "TokenReviewStatus is the result of the token authentication request.",
"properties": { "properties": {
"audiences": {
"description": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.",
"type": "array",
"items": {
"type": "string"
}
},
"authenticated": { "authenticated": {
"description": "Authenticated indicates that the token was associated with a known user.", "description": "Authenticated indicates that the token was associated with a known user.",
"type": "boolean" "type": "boolean"
...@@ -80961,6 +80975,13 @@ ...@@ -80961,6 +80975,13 @@
"io.k8s.api.authentication.v1beta1.TokenReviewSpec": { "io.k8s.api.authentication.v1beta1.TokenReviewSpec": {
"description": "TokenReviewSpec is a description of the token authentication request.", "description": "TokenReviewSpec is a description of the token authentication request.",
"properties": { "properties": {
"audiences": {
"description": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.",
"type": "array",
"items": {
"type": "string"
}
},
"token": { "token": {
"description": "Token is the opaque bearer token.", "description": "Token is the opaque bearer token.",
"type": "string" "type": "string"
...@@ -80970,6 +80991,13 @@ ...@@ -80970,6 +80991,13 @@
"io.k8s.api.authentication.v1beta1.TokenReviewStatus": { "io.k8s.api.authentication.v1beta1.TokenReviewStatus": {
"description": "TokenReviewStatus is the result of the token authentication request.", "description": "TokenReviewStatus is the result of the token authentication request.",
"properties": { "properties": {
"audiences": {
"description": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.",
"type": "array",
"items": {
"type": "string"
}
},
"authenticated": { "authenticated": {
"description": "Authenticated indicates that the token was associated with a known user.", "description": "Authenticated indicates that the token was associated with a known user.",
"type": "boolean" "type": "boolean"
...@@ -395,6 +395,13 @@ ...@@ -395,6 +395,13 @@
"token": { "token": {
"type": "string", "type": "string",
"description": "Token is the opaque bearer token." "description": "Token is the opaque bearer token."
},
"audiences": {
"type": "array",
"items": {
"type": "string"
},
"description": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver."
} }
} }
}, },
...@@ -410,6 +417,13 @@ ...@@ -410,6 +417,13 @@
"$ref": "v1.UserInfo", "$ref": "v1.UserInfo",
"description": "User is the UserInfo associated with the provided token." "description": "User is the UserInfo associated with the provided token."
}, },
"audiences": {
"type": "array",
"items": {
"type": "string"
},
"description": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server."
},
"error": { "error": {
"type": "string", "type": "string",
"description": "Error indicates that the token couldn't be checked" "description": "Error indicates that the token couldn't be checked"
......
...@@ -395,6 +395,13 @@ ...@@ -395,6 +395,13 @@
"token": { "token": {
"type": "string", "type": "string",
"description": "Token is the opaque bearer token." "description": "Token is the opaque bearer token."
},
"audiences": {
"type": "array",
"items": {
"type": "string"
},
"description": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver."
} }
} }
}, },
...@@ -410,6 +417,13 @@ ...@@ -410,6 +417,13 @@
"$ref": "v1beta1.UserInfo", "$ref": "v1beta1.UserInfo",
"description": "User is the UserInfo associated with the provided token." "description": "User is the UserInfo associated with the provided token."
}, },
"audiences": {
"type": "array",
"items": {
"type": "string"
},
"description": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server."
},
"error": { "error": {
"type": "string", "type": "string",
"description": "Error indicates that the token couldn't be checked" "description": "Error indicates that the token couldn't be checked"
......
...@@ -1053,6 +1053,13 @@ When an object is created, the system will populate this list with the current s ...@@ -1053,6 +1053,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">audiences</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -1095,6 +1102,13 @@ When an object is created, the system will populate this list with the current s ...@@ -1095,6 +1102,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">audiences</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token&#8217;s audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is "true", the token is valid against the audience of the Kubernetes API server.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">error</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">error</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Error indicates that the token couldn&#8217;t be checked</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Error indicates that the token couldn&#8217;t be checked</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
......
...@@ -567,6 +567,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } ...@@ -567,6 +567,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">audiences</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -1060,6 +1067,13 @@ When an object is created, the system will populate this list with the current s ...@@ -1060,6 +1067,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">audiences</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token&#8217;s audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is "true", the token is valid against the audience of the Kubernetes API server.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">error</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">error</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Error indicates that the token couldn&#8217;t be checked</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Error indicates that the token couldn&#8217;t be checked</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
......
...@@ -59,6 +59,12 @@ type TokenReview struct { ...@@ -59,6 +59,12 @@ type TokenReview struct {
type TokenReviewSpec struct { type TokenReviewSpec struct {
// Token is the opaque bearer token. // Token is the opaque bearer token.
Token string Token string
// Audiences is a list of the identifiers that the resource server presented
// with the token identifies as. Audience-aware token authenticators will
// verify that the token was intended for at least one of the audiences in
// this list. If no audiences are provided, the audience will default to the
// audience of the Kubernetes apiserver.
Audiences []string
} }
// TokenReviewStatus is the result of the token authentication request. // TokenReviewStatus is the result of the token authentication request.
...@@ -68,6 +74,16 @@ type TokenReviewStatus struct { ...@@ -68,6 +74,16 @@ type TokenReviewStatus struct {
Authenticated bool Authenticated bool
// User is the UserInfo associated with the provided token. // User is the UserInfo associated with the provided token.
User UserInfo User UserInfo
// Audiences are audience identifiers chosen by the authenticator that are
// compatible with both the TokenReview and token. An identifier is any
// identifier in the intersection of the TokenReviewSpec audiences and the
// token's audiences. A client of the TokenReview API that sets the
// spec.audiences field should validate that a compatible audience identifier
// is returned in the status.audiences field to ensure that the TokenReview
// server is audience aware. If a TokenReview returns an empty
// status.audience field where status.authenticated is "true", the token is
// valid against the audience of the Kubernetes API server.
Audiences []string
// Error indicates that the token couldn't be checked // Error indicates that the token couldn't be checked
Error string Error string
} }
......
...@@ -263,6 +263,7 @@ func Convert_authentication_TokenReview_To_v1_TokenReview(in *authentication.Tok ...@@ -263,6 +263,7 @@ func Convert_authentication_TokenReview_To_v1_TokenReview(in *authentication.Tok
func autoConvert_v1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1.TokenReviewSpec, out *authentication.TokenReviewSpec, s conversion.Scope) error { func autoConvert_v1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1.TokenReviewSpec, out *authentication.TokenReviewSpec, s conversion.Scope) error {
out.Token = in.Token out.Token = in.Token
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
return nil return nil
} }
...@@ -273,6 +274,7 @@ func Convert_v1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1.TokenRe ...@@ -273,6 +274,7 @@ func Convert_v1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1.TokenRe
func autoConvert_authentication_TokenReviewSpec_To_v1_TokenReviewSpec(in *authentication.TokenReviewSpec, out *v1.TokenReviewSpec, s conversion.Scope) error { func autoConvert_authentication_TokenReviewSpec_To_v1_TokenReviewSpec(in *authentication.TokenReviewSpec, out *v1.TokenReviewSpec, s conversion.Scope) error {
out.Token = in.Token out.Token = in.Token
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
return nil return nil
} }
...@@ -286,6 +288,7 @@ func autoConvert_v1_TokenReviewStatus_To_authentication_TokenReviewStatus(in *v1 ...@@ -286,6 +288,7 @@ func autoConvert_v1_TokenReviewStatus_To_authentication_TokenReviewStatus(in *v1
if err := Convert_v1_UserInfo_To_authentication_UserInfo(&in.User, &out.User, s); err != nil { if err := Convert_v1_UserInfo_To_authentication_UserInfo(&in.User, &out.User, s); err != nil {
return err return err
} }
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
out.Error = in.Error out.Error = in.Error
return nil return nil
} }
...@@ -300,6 +303,7 @@ func autoConvert_authentication_TokenReviewStatus_To_v1_TokenReviewStatus(in *au ...@@ -300,6 +303,7 @@ func autoConvert_authentication_TokenReviewStatus_To_v1_TokenReviewStatus(in *au
if err := Convert_authentication_UserInfo_To_v1_UserInfo(&in.User, &out.User, s); err != nil { if err := Convert_authentication_UserInfo_To_v1_UserInfo(&in.User, &out.User, s); err != nil {
return err return err
} }
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
out.Error = in.Error out.Error = in.Error
return nil return nil
} }
......
...@@ -113,6 +113,7 @@ func Convert_authentication_TokenReview_To_v1beta1_TokenReview(in *authenticatio ...@@ -113,6 +113,7 @@ func Convert_authentication_TokenReview_To_v1beta1_TokenReview(in *authenticatio
func autoConvert_v1beta1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1beta1.TokenReviewSpec, out *authentication.TokenReviewSpec, s conversion.Scope) error { func autoConvert_v1beta1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1beta1.TokenReviewSpec, out *authentication.TokenReviewSpec, s conversion.Scope) error {
out.Token = in.Token out.Token = in.Token
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
return nil return nil
} }
...@@ -123,6 +124,7 @@ func Convert_v1beta1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1bet ...@@ -123,6 +124,7 @@ func Convert_v1beta1_TokenReviewSpec_To_authentication_TokenReviewSpec(in *v1bet
func autoConvert_authentication_TokenReviewSpec_To_v1beta1_TokenReviewSpec(in *authentication.TokenReviewSpec, out *v1beta1.TokenReviewSpec, s conversion.Scope) error { func autoConvert_authentication_TokenReviewSpec_To_v1beta1_TokenReviewSpec(in *authentication.TokenReviewSpec, out *v1beta1.TokenReviewSpec, s conversion.Scope) error {
out.Token = in.Token out.Token = in.Token
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
return nil return nil
} }
...@@ -136,6 +138,7 @@ func autoConvert_v1beta1_TokenReviewStatus_To_authentication_TokenReviewStatus(i ...@@ -136,6 +138,7 @@ func autoConvert_v1beta1_TokenReviewStatus_To_authentication_TokenReviewStatus(i
if err := Convert_v1beta1_UserInfo_To_authentication_UserInfo(&in.User, &out.User, s); err != nil { if err := Convert_v1beta1_UserInfo_To_authentication_UserInfo(&in.User, &out.User, s); err != nil {
return err return err
} }
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
out.Error = in.Error out.Error = in.Error
return nil return nil
} }
...@@ -150,6 +153,7 @@ func autoConvert_authentication_TokenReviewStatus_To_v1beta1_TokenReviewStatus(i ...@@ -150,6 +153,7 @@ func autoConvert_authentication_TokenReviewStatus_To_v1beta1_TokenReviewStatus(i
if err := Convert_authentication_UserInfo_To_v1beta1_UserInfo(&in.User, &out.User, s); err != nil { if err := Convert_authentication_UserInfo_To_v1beta1_UserInfo(&in.User, &out.User, s); err != nil {
return err return err
} }
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
out.Error = in.Error out.Error = in.Error
return nil return nil
} }
......
...@@ -136,7 +136,7 @@ func (in *TokenReview) DeepCopyInto(out *TokenReview) { ...@@ -136,7 +136,7 @@ func (in *TokenReview) DeepCopyInto(out *TokenReview) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
return return
} }
...@@ -162,6 +162,11 @@ func (in *TokenReview) DeepCopyObject() runtime.Object { ...@@ -162,6 +162,11 @@ func (in *TokenReview) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TokenReviewSpec) DeepCopyInto(out *TokenReviewSpec) { func (in *TokenReviewSpec) DeepCopyInto(out *TokenReviewSpec) {
*out = *in *out = *in
if in.Audiences != nil {
in, out := &in.Audiences, &out.Audiences
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
...@@ -179,6 +184,11 @@ func (in *TokenReviewSpec) DeepCopy() *TokenReviewSpec { ...@@ -179,6 +184,11 @@ func (in *TokenReviewSpec) DeepCopy() *TokenReviewSpec {
func (in *TokenReviewStatus) DeepCopyInto(out *TokenReviewStatus) { func (in *TokenReviewStatus) DeepCopyInto(out *TokenReviewStatus) {
*out = *in *out = *in
in.User.DeepCopyInto(&out.User) in.User.DeepCopyInto(&out.User)
if in.Audiences != nil {
in, out := &in.Audiences, &out.Audiences
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -179,7 +179,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er ...@@ -179,7 +179,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
tokenAuthenticators = append(tokenAuthenticators, oidcAuth) tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
} }
if len(config.WebhookTokenAuthnConfigFile) > 0 { if len(config.WebhookTokenAuthnConfigFile) > 0 {
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL) webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL, config.APIAudiences)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -318,8 +318,8 @@ func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Reques ...@@ -318,8 +318,8 @@ func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Reques
return x509.New(opts, x509.CommonNameUserConversion), nil return x509.New(opts, x509.CommonNameUserConversion), nil
} }
func newWebhookTokenAuthenticator(webhookConfigFile string, ttl time.Duration) (authenticator.Token, error) { func newWebhookTokenAuthenticator(webhookConfigFile string, ttl time.Duration, implicitAuds authenticator.Audiences) (authenticator.Token, error) {
webhookTokenAuthenticator, err := webhook.New(webhookConfigFile) webhookTokenAuthenticator, err := webhook.New(webhookConfigFile, implicitAuds)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -335,7 +335,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) ...@@ -335,7 +335,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
// handlers that we have. // handlers that we have.
restStorageProviders := []RESTStorageProvider{ restStorageProviders := []RESTStorageProvider{
auditregistrationrest.RESTStorageProvider{}, auditregistrationrest.RESTStorageProvider{},
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator}, authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator, APIAudiences: c.GenericConfig.Authentication.APIAudiences},
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver}, authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver},
autoscalingrest.RESTStorageProvider{}, autoscalingrest.RESTStorageProvider{},
batchrest.RESTStorageProvider{}, batchrest.RESTStorageProvider{},
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
type RESTStorageProvider struct { type RESTStorageProvider struct {
Authenticator authenticator.Request Authenticator authenticator.Request
APIAudiences authenticator.Audiences
} }
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) {
...@@ -56,7 +57,7 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag ...@@ -56,7 +57,7 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag
func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
storage := map[string]rest.Storage{} storage := map[string]rest.Storage{}
// tokenreviews // tokenreviews
tokenReviewStorage := tokenreview.NewREST(p.Authenticator) tokenReviewStorage := tokenreview.NewREST(p.Authenticator, p.APIAudiences)
storage["tokenreviews"] = tokenReviewStorage storage["tokenreviews"] = tokenReviewStorage
return storage return storage
...@@ -65,7 +66,7 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag ...@@ -65,7 +66,7 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag
func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
storage := map[string]rest.Storage{} storage := map[string]rest.Storage{}
// tokenreviews // tokenreviews
tokenReviewStorage := tokenreview.NewREST(p.Authenticator) tokenReviewStorage := tokenreview.NewREST(p.Authenticator, p.APIAudiences)
storage["tokenreviews"] = tokenReviewStorage storage["tokenreviews"] = tokenReviewStorage
return storage return storage
......
...@@ -17,6 +17,7 @@ go_library( ...@@ -17,6 +17,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
], ],
) )
......
...@@ -18,6 +18,7 @@ package tokenreview ...@@ -18,6 +18,7 @@ package tokenreview
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
...@@ -27,15 +28,22 @@ import ( ...@@ -27,15 +28,22 @@ import (
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/apis/authentication" "k8s.io/kubernetes/pkg/apis/authentication"
) )
var badAuthenticatorAuds = apierrors.NewInternalError(errors.New("error validating audiences"))
type REST struct { type REST struct {
tokenAuthenticator authenticator.Request tokenAuthenticator authenticator.Request
apiAudiences []string
} }
func NewREST(tokenAuthenticator authenticator.Request) *REST { func NewREST(tokenAuthenticator authenticator.Request, apiAudiences []string) *REST {
return &REST{tokenAuthenticator: tokenAuthenticator} return &REST{
tokenAuthenticator: tokenAuthenticator,
apiAudiences: apiAudiences,
}
} }
func (r *REST) NamespaceScoped() bool { func (r *REST) NamespaceScoped() bool {
...@@ -68,14 +76,24 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation ...@@ -68,14 +76,24 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation
fakeReq := &http.Request{Header: http.Header{}} fakeReq := &http.Request{Header: http.Header{}}
fakeReq.Header.Add("Authorization", "Bearer "+tokenReview.Spec.Token) fakeReq.Header.Add("Authorization", "Bearer "+tokenReview.Spec.Token)
auds := tokenReview.Spec.Audiences
if len(auds) == 0 {
auds = r.apiAudiences
}
if len(auds) > 0 {
fakeReq = fakeReq.WithContext(authenticator.WithAudiences(fakeReq.Context(), auds))
}
resp, ok, err := r.tokenAuthenticator.AuthenticateRequest(fakeReq) resp, ok, err := r.tokenAuthenticator.AuthenticateRequest(fakeReq)
tokenReview.Status.Authenticated = ok tokenReview.Status.Authenticated = ok
if err != nil { if err != nil {
tokenReview.Status.Error = err.Error() tokenReview.Status.Error = err.Error()
} }
// TODO(mikedanese): verify the response audience matches one of apiAuds if if len(auds) > 0 && resp != nil && len(authenticator.Audiences(auds).Intersect(resp.Audiences)) == 0 {
// non-empty klog.Errorf("error validating audience. want=%q got=%q", auds, resp.Audiences)
return nil, badAuthenticatorAuds
}
if resp != nil && resp.User != nil { if resp != nil && resp.User != nil {
tokenReview.Status.User = authentication.UserInfo{ tokenReview.Status.User = authentication.UserInfo{
...@@ -87,6 +105,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation ...@@ -87,6 +105,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation
for k, v := range resp.User.GetExtra() { for k, v := range resp.User.GetExtra() {
tokenReview.Status.User.Extra[k] = authentication.ExtraValue(v) tokenReview.Status.User.Extra[k] = authentication.ExtraValue(v)
} }
tokenReview.Status.Audiences = resp.Audiences
} }
return tokenReview, nil return tokenReview, nil
......
...@@ -118,6 +118,14 @@ message TokenReviewSpec { ...@@ -118,6 +118,14 @@ message TokenReviewSpec {
// Token is the opaque bearer token. // Token is the opaque bearer token.
// +optional // +optional
optional string token = 1; optional string token = 1;
// Audiences is a list of the identifiers that the resource server presented
// with the token identifies as. Audience-aware token authenticators will
// verify that the token was intended for at least one of the audiences in
// this list. If no audiences are provided, the audience will default to the
// audience of the Kubernetes apiserver.
// +optional
repeated string audiences = 2;
} }
// TokenReviewStatus is the result of the token authentication request. // TokenReviewStatus is the result of the token authentication request.
...@@ -130,6 +138,18 @@ message TokenReviewStatus { ...@@ -130,6 +138,18 @@ message TokenReviewStatus {
// +optional // +optional
optional UserInfo user = 2; optional UserInfo user = 2;
// Audiences are audience identifiers chosen by the authenticator that are
// compatible with both the TokenReview and token. An identifier is any
// identifier in the intersection of the TokenReviewSpec audiences and the
// token's audiences. A client of the TokenReview API that sets the
// spec.audiences field should validate that a compatible audience identifier
// is returned in the status.audiences field to ensure that the TokenReview
// server is audience aware. If a TokenReview returns an empty
// status.audience field where status.authenticated is "true", the token is
// valid against the audience of the Kubernetes API server.
// +optional
repeated string audiences = 4;
// Error indicates that the token couldn't be checked // Error indicates that the token couldn't be checked
// +optional // +optional
optional string error = 3; optional string error = 3;
......
...@@ -64,6 +64,13 @@ type TokenReviewSpec struct { ...@@ -64,6 +64,13 @@ type TokenReviewSpec struct {
// Token is the opaque bearer token. // Token is the opaque bearer token.
// +optional // +optional
Token string `json:"token,omitempty" protobuf:"bytes,1,opt,name=token"` Token string `json:"token,omitempty" protobuf:"bytes,1,opt,name=token"`
// Audiences is a list of the identifiers that the resource server presented
// with the token identifies as. Audience-aware token authenticators will
// verify that the token was intended for at least one of the audiences in
// this list. If no audiences are provided, the audience will default to the
// audience of the Kubernetes apiserver.
// +optional
Audiences []string `json:"audiences,omitempty" protobuf:"bytes,2,rep,name=audiences"`
} }
// TokenReviewStatus is the result of the token authentication request. // TokenReviewStatus is the result of the token authentication request.
...@@ -74,6 +81,17 @@ type TokenReviewStatus struct { ...@@ -74,6 +81,17 @@ type TokenReviewStatus struct {
// User is the UserInfo associated with the provided token. // User is the UserInfo associated with the provided token.
// +optional // +optional
User UserInfo `json:"user,omitempty" protobuf:"bytes,2,opt,name=user"` User UserInfo `json:"user,omitempty" protobuf:"bytes,2,opt,name=user"`
// Audiences are audience identifiers chosen by the authenticator that are
// compatible with both the TokenReview and token. An identifier is any
// identifier in the intersection of the TokenReviewSpec audiences and the
// token's audiences. A client of the TokenReview API that sets the
// spec.audiences field should validate that a compatible audience identifier
// is returned in the status.audiences field to ensure that the TokenReview
// server is audience aware. If a TokenReview returns an empty
// status.audience field where status.authenticated is "true", the token is
// valid against the audience of the Kubernetes API server.
// +optional
Audiences []string `json:"audiences,omitempty" protobuf:"bytes,4,rep,name=audiences"`
// Error indicates that the token couldn't be checked // Error indicates that the token couldn't be checked
// +optional // +optional
Error string `json:"error,omitempty" protobuf:"bytes,3,opt,name=error"` Error string `json:"error,omitempty" protobuf:"bytes,3,opt,name=error"`
......
...@@ -79,8 +79,9 @@ func (TokenReview) SwaggerDoc() map[string]string { ...@@ -79,8 +79,9 @@ func (TokenReview) SwaggerDoc() map[string]string {
} }
var map_TokenReviewSpec = map[string]string{ var map_TokenReviewSpec = map[string]string{
"": "TokenReviewSpec is a description of the token authentication request.", "": "TokenReviewSpec is a description of the token authentication request.",
"token": "Token is the opaque bearer token.", "token": "Token is the opaque bearer token.",
"audiences": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.",
} }
func (TokenReviewSpec) SwaggerDoc() map[string]string { func (TokenReviewSpec) SwaggerDoc() map[string]string {
...@@ -91,6 +92,7 @@ var map_TokenReviewStatus = map[string]string{ ...@@ -91,6 +92,7 @@ var map_TokenReviewStatus = map[string]string{
"": "TokenReviewStatus is the result of the token authentication request.", "": "TokenReviewStatus is the result of the token authentication request.",
"authenticated": "Authenticated indicates that the token was associated with a known user.", "authenticated": "Authenticated indicates that the token was associated with a known user.",
"user": "User is the UserInfo associated with the provided token.", "user": "User is the UserInfo associated with the provided token.",
"audiences": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.",
"error": "Error indicates that the token couldn't be checked", "error": "Error indicates that the token couldn't be checked",
} }
......
...@@ -141,7 +141,7 @@ func (in *TokenReview) DeepCopyInto(out *TokenReview) { ...@@ -141,7 +141,7 @@ func (in *TokenReview) DeepCopyInto(out *TokenReview) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
return return
} }
...@@ -167,6 +167,11 @@ func (in *TokenReview) DeepCopyObject() runtime.Object { ...@@ -167,6 +167,11 @@ func (in *TokenReview) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TokenReviewSpec) DeepCopyInto(out *TokenReviewSpec) { func (in *TokenReviewSpec) DeepCopyInto(out *TokenReviewSpec) {
*out = *in *out = *in
if in.Audiences != nil {
in, out := &in.Audiences, &out.Audiences
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
...@@ -184,6 +189,11 @@ func (in *TokenReviewSpec) DeepCopy() *TokenReviewSpec { ...@@ -184,6 +189,11 @@ func (in *TokenReviewSpec) DeepCopy() *TokenReviewSpec {
func (in *TokenReviewStatus) DeepCopyInto(out *TokenReviewStatus) { func (in *TokenReviewStatus) DeepCopyInto(out *TokenReviewStatus) {
*out = *in *out = *in
in.User.DeepCopyInto(&out.User) in.User.DeepCopyInto(&out.User)
if in.Audiences != nil {
in, out := &in.Audiences, &out.Audiences
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -57,6 +57,14 @@ message TokenReviewSpec { ...@@ -57,6 +57,14 @@ message TokenReviewSpec {
// Token is the opaque bearer token. // Token is the opaque bearer token.
// +optional // +optional
optional string token = 1; optional string token = 1;
// Audiences is a list of the identifiers that the resource server presented
// with the token identifies as. Audience-aware token authenticators will
// verify that the token was intended for at least one of the audiences in
// this list. If no audiences are provided, the audience will default to the
// audience of the Kubernetes apiserver.
// +optional
repeated string audiences = 2;
} }
// TokenReviewStatus is the result of the token authentication request. // TokenReviewStatus is the result of the token authentication request.
...@@ -69,6 +77,18 @@ message TokenReviewStatus { ...@@ -69,6 +77,18 @@ message TokenReviewStatus {
// +optional // +optional
optional UserInfo user = 2; optional UserInfo user = 2;
// Audiences are audience identifiers chosen by the authenticator that are
// compatible with both the TokenReview and token. An identifier is any
// identifier in the intersection of the TokenReviewSpec audiences and the
// token's audiences. A client of the TokenReview API that sets the
// spec.audiences field should validate that a compatible audience identifier
// is returned in the status.audiences field to ensure that the TokenReview
// server is audience aware. If a TokenReview returns an empty
// status.audience field where status.authenticated is "true", the token is
// valid against the audience of the Kubernetes API server.
// +optional
repeated string audiences = 4;
// Error indicates that the token couldn't be checked // Error indicates that the token couldn't be checked
// +optional // +optional
optional string error = 3; optional string error = 3;
......
...@@ -48,6 +48,13 @@ type TokenReviewSpec struct { ...@@ -48,6 +48,13 @@ type TokenReviewSpec struct {
// Token is the opaque bearer token. // Token is the opaque bearer token.
// +optional // +optional
Token string `json:"token,omitempty" protobuf:"bytes,1,opt,name=token"` Token string `json:"token,omitempty" protobuf:"bytes,1,opt,name=token"`
// Audiences is a list of the identifiers that the resource server presented
// with the token identifies as. Audience-aware token authenticators will
// verify that the token was intended for at least one of the audiences in
// this list. If no audiences are provided, the audience will default to the
// audience of the Kubernetes apiserver.
// +optional
Audiences []string `json:"audiences,omitempty" protobuf:"bytes,2,rep,name=audiences"`
} }
// TokenReviewStatus is the result of the token authentication request. // TokenReviewStatus is the result of the token authentication request.
...@@ -58,6 +65,17 @@ type TokenReviewStatus struct { ...@@ -58,6 +65,17 @@ type TokenReviewStatus struct {
// User is the UserInfo associated with the provided token. // User is the UserInfo associated with the provided token.
// +optional // +optional
User UserInfo `json:"user,omitempty" protobuf:"bytes,2,opt,name=user"` User UserInfo `json:"user,omitempty" protobuf:"bytes,2,opt,name=user"`
// Audiences are audience identifiers chosen by the authenticator that are
// compatible with both the TokenReview and token. An identifier is any
// identifier in the intersection of the TokenReviewSpec audiences and the
// token's audiences. A client of the TokenReview API that sets the
// spec.audiences field should validate that a compatible audience identifier
// is returned in the status.audiences field to ensure that the TokenReview
// server is audience aware. If a TokenReview returns an empty
// status.audience field where status.authenticated is "true", the token is
// valid against the audience of the Kubernetes API server.
// +optional
Audiences []string `json:"audiences,omitempty" protobuf:"bytes,4,rep,name=audiences"`
// Error indicates that the token couldn't be checked // Error indicates that the token couldn't be checked
// +optional // +optional
Error string `json:"error,omitempty" protobuf:"bytes,3,opt,name=error"` Error string `json:"error,omitempty" protobuf:"bytes,3,opt,name=error"`
......
...@@ -38,8 +38,9 @@ func (TokenReview) SwaggerDoc() map[string]string { ...@@ -38,8 +38,9 @@ func (TokenReview) SwaggerDoc() map[string]string {
} }
var map_TokenReviewSpec = map[string]string{ var map_TokenReviewSpec = map[string]string{
"": "TokenReviewSpec is a description of the token authentication request.", "": "TokenReviewSpec is a description of the token authentication request.",
"token": "Token is the opaque bearer token.", "token": "Token is the opaque bearer token.",
"audiences": "Audiences is a list of the identifiers that the resource server presented with the token identifies as. Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver.",
} }
func (TokenReviewSpec) SwaggerDoc() map[string]string { func (TokenReviewSpec) SwaggerDoc() map[string]string {
...@@ -50,6 +51,7 @@ var map_TokenReviewStatus = map[string]string{ ...@@ -50,6 +51,7 @@ var map_TokenReviewStatus = map[string]string{
"": "TokenReviewStatus is the result of the token authentication request.", "": "TokenReviewStatus is the result of the token authentication request.",
"authenticated": "Authenticated indicates that the token was associated with a known user.", "authenticated": "Authenticated indicates that the token was associated with a known user.",
"user": "User is the UserInfo associated with the provided token.", "user": "User is the UserInfo associated with the provided token.",
"audiences": "Audiences are audience identifiers chosen by the authenticator that are compatible with both the TokenReview and token. An identifier is any identifier in the intersection of the TokenReviewSpec audiences and the token's audiences. A client of the TokenReview API that sets the spec.audiences field should validate that a compatible audience identifier is returned in the status.audiences field to ensure that the TokenReview server is audience aware. If a TokenReview returns an empty status.audience field where status.authenticated is \"true\", the token is valid against the audience of the Kubernetes API server.",
"error": "Error indicates that the token couldn't be checked", "error": "Error indicates that the token couldn't be checked",
} }
......
...@@ -49,7 +49,7 @@ func (in *TokenReview) DeepCopyInto(out *TokenReview) { ...@@ -49,7 +49,7 @@ func (in *TokenReview) DeepCopyInto(out *TokenReview) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
return return
} }
...@@ -75,6 +75,11 @@ func (in *TokenReview) DeepCopyObject() runtime.Object { ...@@ -75,6 +75,11 @@ func (in *TokenReview) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TokenReviewSpec) DeepCopyInto(out *TokenReviewSpec) { func (in *TokenReviewSpec) DeepCopyInto(out *TokenReviewSpec) {
*out = *in *out = *in
if in.Audiences != nil {
in, out := &in.Audiences, &out.Audiences
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
...@@ -92,6 +97,11 @@ func (in *TokenReviewSpec) DeepCopy() *TokenReviewSpec { ...@@ -92,6 +97,11 @@ func (in *TokenReviewSpec) DeepCopy() *TokenReviewSpec {
func (in *TokenReviewStatus) DeepCopyInto(out *TokenReviewStatus) { func (in *TokenReviewStatus) DeepCopyInto(out *TokenReviewStatus) {
*out = *in *out = *in
in.User.DeepCopyInto(&out.User) in.User.DeepCopyInto(&out.User)
if in.Audiences != nil {
in, out := &in.Audiences, &out.Audiences
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -51,6 +51,8 @@ type DelegatingAuthenticatorConfig struct { ...@@ -51,6 +51,8 @@ type DelegatingAuthenticatorConfig struct {
// ClientCAFile is the CA bundle file used to authenticate client certificates // ClientCAFile is the CA bundle file used to authenticate client certificates
ClientCAFile string ClientCAFile string
APIAudiences authenticator.Audiences
RequestHeaderConfig *RequestHeaderConfig RequestHeaderConfig *RequestHeaderConfig
} }
...@@ -86,7 +88,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur ...@@ -86,7 +88,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
} }
if c.TokenAccessReviewClient != nil { if c.TokenAccessReviewClient != nil {
tokenAuth, err := webhooktoken.NewFromInterface(c.TokenAccessReviewClient) tokenAuth, err := webhooktoken.NewFromInterface(c.TokenAccessReviewClient, c.APIAudiences)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
......
...@@ -21,8 +21,6 @@ import ( ...@@ -21,8 +21,6 @@ import (
"context" "context"
"time" "time"
"k8s.io/klog"
authentication "k8s.io/api/authentication/v1beta1" authentication "k8s.io/api/authentication/v1beta1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
...@@ -31,6 +29,7 @@ import ( ...@@ -31,6 +29,7 @@ import (
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1" authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
"k8s.io/klog"
) )
var ( var (
...@@ -45,38 +44,58 @@ var _ authenticator.Token = (*WebhookTokenAuthenticator)(nil) ...@@ -45,38 +44,58 @@ var _ authenticator.Token = (*WebhookTokenAuthenticator)(nil)
type WebhookTokenAuthenticator struct { type WebhookTokenAuthenticator struct {
tokenReview authenticationclient.TokenReviewInterface tokenReview authenticationclient.TokenReviewInterface
initialBackoff time.Duration initialBackoff time.Duration
implicitAuds authenticator.Audiences
} }
// NewFromInterface creates a webhook authenticator using the given tokenReview // NewFromInterface creates a webhook authenticator using the given tokenReview
// client. It is recommend to wrap this authenticator with the token cache // client. It is recommend to wrap this authenticator with the token cache
// authenticator implemented in // authenticator implemented in
// k8s.io/apiserver/pkg/authentication/token/cache. // k8s.io/apiserver/pkg/authentication/token/cache.
func NewFromInterface(tokenReview authenticationclient.TokenReviewInterface) (*WebhookTokenAuthenticator, error) { func NewFromInterface(tokenReview authenticationclient.TokenReviewInterface, implicitAuds authenticator.Audiences) (*WebhookTokenAuthenticator, error) {
return newWithBackoff(tokenReview, retryBackoff) return newWithBackoff(tokenReview, retryBackoff, implicitAuds)
} }
// New creates a new WebhookTokenAuthenticator from the provided kubeconfig file. // New creates a new WebhookTokenAuthenticator from the provided kubeconfig
func New(kubeConfigFile string) (*WebhookTokenAuthenticator, error) { // file. It is recommend to wrap this authenticator with the token cache
// authenticator implemented in
// k8s.io/apiserver/pkg/authentication/token/cache.
func New(kubeConfigFile string, implicitAuds authenticator.Audiences) (*WebhookTokenAuthenticator, error) {
tokenReview, err := tokenReviewInterfaceFromKubeconfig(kubeConfigFile) tokenReview, err := tokenReviewInterfaceFromKubeconfig(kubeConfigFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newWithBackoff(tokenReview, retryBackoff) return newWithBackoff(tokenReview, retryBackoff, implicitAuds)
} }
// newWithBackoff allows tests to skip the sleep. // newWithBackoff allows tests to skip the sleep.
func newWithBackoff(tokenReview authenticationclient.TokenReviewInterface, initialBackoff time.Duration) (*WebhookTokenAuthenticator, error) { func newWithBackoff(tokenReview authenticationclient.TokenReviewInterface, initialBackoff time.Duration, implicitAuds authenticator.Audiences) (*WebhookTokenAuthenticator, error) {
return &WebhookTokenAuthenticator{tokenReview, initialBackoff}, nil return &WebhookTokenAuthenticator{tokenReview, initialBackoff, implicitAuds}, nil
} }
// AuthenticateToken implements the authenticator.Token interface. // AuthenticateToken implements the authenticator.Token interface.
func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) { func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
// We take implicit audiences of the API server at WebhookTokenAuthenticator
// construction time. The outline of how we validate audience here is:
//
// * if the ctx is not audience limited, don't do any audience validation.
// * if ctx is audience-limited, add the audiences to the tokenreview spec
// * if the tokenreview returns with audiences in the status that intersect
// with the audiences in the ctx, copy into the response and return success
// * if the tokenreview returns without an audience in the status, ensure
// the ctx audiences intersect with the implicit audiences, and set the
// intersection in the response.
// * otherwise return unauthenticated.
wantAuds, checkAuds := authenticator.AudiencesFrom(ctx)
r := &authentication.TokenReview{ r := &authentication.TokenReview{
Spec: authentication.TokenReviewSpec{Token: token}, Spec: authentication.TokenReviewSpec{
Token: token,
Audiences: wantAuds,
},
} }
var ( var (
result *authentication.TokenReview result *authentication.TokenReview
err error err error
auds authenticator.Audiences
) )
webhook.WithExponentialBackoff(w.initialBackoff, func() error { webhook.WithExponentialBackoff(w.initialBackoff, func() error {
result, err = w.tokenReview.Create(r) result, err = w.tokenReview.Create(r)
...@@ -87,6 +106,18 @@ func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token ...@@ -87,6 +106,18 @@ func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token
klog.Errorf("Failed to make webhook authenticator request: %v", err) klog.Errorf("Failed to make webhook authenticator request: %v", err)
return nil, false, err return nil, false, err
} }
if checkAuds {
gotAuds := w.implicitAuds
if len(result.Status.Audiences) > 0 {
gotAuds = result.Status.Audiences
}
auds = wantAuds.Intersect(gotAuds)
if len(auds) == 0 {
return nil, false, nil
}
}
r.Status = result.Status r.Status = result.Status
if !r.Status.Authenticated { if !r.Status.Authenticated {
return nil, false, nil return nil, false, nil
...@@ -107,6 +138,7 @@ func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token ...@@ -107,6 +138,7 @@ func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token
Groups: r.Status.User.Groups, Groups: r.Status.User.Groups,
Extra: extra, Extra: extra,
}, },
Audiences: auds,
}, true, nil }, true, nil
} }
......
...@@ -39,6 +39,8 @@ import ( ...@@ -39,6 +39,8 @@ import (
"k8s.io/client-go/tools/clientcmd/api/v1" "k8s.io/client-go/tools/clientcmd/api/v1"
) )
var apiAuds = authenticator.Audiences{"api"}
// Service mocks a remote authentication service. // Service mocks a remote authentication service.
type Service interface { type Service interface {
// Review looks at the TokenReviewSpec and provides an authentication // Review looks at the TokenReviewSpec and provides an authentication
...@@ -105,6 +107,7 @@ func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error ...@@ -105,6 +107,7 @@ func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error
type status struct { type status struct {
Authenticated bool `json:"authenticated"` Authenticated bool `json:"authenticated"`
User userInfo `json:"user"` User userInfo `json:"user"`
Audiences []string `json:"audiences"`
} }
var extra map[string][]string var extra map[string][]string
...@@ -130,6 +133,7 @@ func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error ...@@ -130,6 +133,7 @@ func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error
Groups: review.Status.User.Groups, Groups: review.Status.User.Groups,
Extra: extra, Extra: extra,
}, },
review.Status.Audiences,
}, },
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
...@@ -168,7 +172,7 @@ func (m *mockService) HTTPStatusCode() int { return m.statusCode } ...@@ -168,7 +172,7 @@ func (m *mockService) HTTPStatusCode() int { return m.statusCode }
// newTokenAuthenticator creates a temporary kubeconfig file from the provided // newTokenAuthenticator creates a temporary kubeconfig file from the provided
// arguments and attempts to load a new WebhookTokenAuthenticator from it. // arguments and attempts to load a new WebhookTokenAuthenticator from it.
func newTokenAuthenticator(serverURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration) (authenticator.Token, error) { func newTokenAuthenticator(serverURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, implicitAuds authenticator.Audiences) (authenticator.Token, error) {
tempfile, err := ioutil.TempFile("", "") tempfile, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -196,7 +200,7 @@ func newTokenAuthenticator(serverURL string, clientCert, clientKey, ca []byte, c ...@@ -196,7 +200,7 @@ func newTokenAuthenticator(serverURL string, clientCert, clientKey, ca []byte, c
return nil, err return nil, err
} }
authn, err := newWithBackoff(c, 0) authn, err := newWithBackoff(c, 0, implicitAuds)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -257,7 +261,7 @@ func TestTLSConfig(t *testing.T) { ...@@ -257,7 +261,7 @@ func TestTLSConfig(t *testing.T) {
} }
defer server.Close() defer server.Close()
wh, err := newTokenAuthenticator(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, 0) wh, err := newTokenAuthenticator(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, 0, nil)
if err != nil { if err != nil {
t.Errorf("%s: failed to create client: %v", tt.test, err) t.Errorf("%s: failed to create client: %v", tt.test, err)
return return
...@@ -312,23 +316,21 @@ func TestWebhookTokenAuthenticator(t *testing.T) { ...@@ -312,23 +316,21 @@ func TestWebhookTokenAuthenticator(t *testing.T) {
} }
defer s.Close() defer s.Close()
wh, err := newTokenAuthenticator(s.URL, clientCert, clientKey, caCert, 0)
if err != nil {
t.Fatal(err)
}
expTypeMeta := metav1.TypeMeta{ expTypeMeta := metav1.TypeMeta{
APIVersion: "authentication.k8s.io/v1beta1", APIVersion: "authentication.k8s.io/v1beta1",
Kind: "TokenReview", Kind: "TokenReview",
} }
tests := []struct { tests := []struct {
description string
implicitAuds, reqAuds authenticator.Audiences
serverResponse v1beta1.TokenReviewStatus serverResponse v1beta1.TokenReviewStatus
expectedAuthenticated bool expectedAuthenticated bool
expectedUser *user.DefaultInfo expectedUser *user.DefaultInfo
expectedAuds authenticator.Audiences
}{ }{
// Successful response should pass through all user info.
{ {
description: "successful response should pass through all user info.",
serverResponse: v1beta1.TokenReviewStatus{ serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true, Authenticated: true,
User: v1beta1.UserInfo{ User: v1beta1.UserInfo{
...@@ -341,6 +343,7 @@ func TestWebhookTokenAuthenticator(t *testing.T) { ...@@ -341,6 +343,7 @@ func TestWebhookTokenAuthenticator(t *testing.T) {
}, },
}, },
{ {
description: "successful response should pass through all user info.",
serverResponse: v1beta1.TokenReviewStatus{ serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true, Authenticated: true,
User: v1beta1.UserInfo{ User: v1beta1.UserInfo{
...@@ -358,8 +361,8 @@ func TestWebhookTokenAuthenticator(t *testing.T) { ...@@ -358,8 +361,8 @@ func TestWebhookTokenAuthenticator(t *testing.T) {
Extra: map[string][]string{"foo": {"bar", "baz"}}, Extra: map[string][]string{"foo": {"bar", "baz"}},
}, },
}, },
// Unauthenticated shouldn't even include extra provided info.
{ {
description: "unauthenticated shouldn't even include extra provided info.",
serverResponse: v1beta1.TokenReviewStatus{ serverResponse: v1beta1.TokenReviewStatus{
Authenticated: false, Authenticated: false,
User: v1beta1.UserInfo{ User: v1beta1.UserInfo{
...@@ -372,37 +375,151 @@ func TestWebhookTokenAuthenticator(t *testing.T) { ...@@ -372,37 +375,151 @@ func TestWebhookTokenAuthenticator(t *testing.T) {
expectedUser: nil, expectedUser: nil,
}, },
{ {
description: "unauthenticated shouldn't even include extra provided info.",
serverResponse: v1beta1.TokenReviewStatus{ serverResponse: v1beta1.TokenReviewStatus{
Authenticated: false, Authenticated: false,
}, },
expectedAuthenticated: false, expectedAuthenticated: false,
expectedUser: nil, expectedUser: nil,
}, },
{
description: "good audience",
implicitAuds: apiAuds,
reqAuds: apiAuds,
serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true,
User: v1beta1.UserInfo{
Username: "somebody",
},
},
expectedAuthenticated: true,
expectedUser: &user.DefaultInfo{
Name: "somebody",
},
expectedAuds: apiAuds,
},
{
description: "good audience",
implicitAuds: append(apiAuds, "other"),
reqAuds: apiAuds,
serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true,
User: v1beta1.UserInfo{
Username: "somebody",
},
},
expectedAuthenticated: true,
expectedUser: &user.DefaultInfo{
Name: "somebody",
},
expectedAuds: apiAuds,
},
{
description: "bad audiences",
implicitAuds: apiAuds,
reqAuds: authenticator.Audiences{"other"},
serverResponse: v1beta1.TokenReviewStatus{
Authenticated: false,
},
expectedAuthenticated: false,
},
{
description: "bad audiences",
implicitAuds: apiAuds,
reqAuds: authenticator.Audiences{"other"},
// webhook authenticator hasn't been upgraded to support audience.
serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true,
User: v1beta1.UserInfo{
Username: "somebody",
},
},
expectedAuthenticated: false,
},
{
description: "audience aware backend",
implicitAuds: apiAuds,
reqAuds: apiAuds,
serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true,
User: v1beta1.UserInfo{
Username: "somebody",
},
Audiences: []string(apiAuds),
},
expectedAuthenticated: true,
expectedUser: &user.DefaultInfo{
Name: "somebody",
},
expectedAuds: apiAuds,
},
{
description: "audience aware backend",
serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true,
User: v1beta1.UserInfo{
Username: "somebody",
},
Audiences: []string(apiAuds),
},
expectedAuthenticated: true,
expectedUser: &user.DefaultInfo{
Name: "somebody",
},
},
{
description: "audience aware backend",
implicitAuds: apiAuds,
reqAuds: apiAuds,
serverResponse: v1beta1.TokenReviewStatus{
Authenticated: true,
User: v1beta1.UserInfo{
Username: "somebody",
},
Audiences: []string{"other"},
},
expectedAuthenticated: false,
},
} }
token := "my-s3cr3t-t0ken" token := "my-s3cr3t-t0ken"
for i, tt := range tests { for _, tt := range tests {
serv.response = tt.serverResponse t.Run(tt.description, func(t *testing.T) {
resp, authenticated, err := wh.AuthenticateToken(context.Background(), token) wh, err := newTokenAuthenticator(s.URL, clientCert, clientKey, caCert, 0, tt.implicitAuds)
if err != nil { if err != nil {
t.Errorf("case %d: authentication failed: %v", i, err) t.Fatal(err)
continue }
}
if serv.lastRequest.Spec.Token != token { ctx := context.Background()
t.Errorf("case %d: Server did not see correct token. Got %q, expected %q.", if tt.reqAuds != nil {
i, serv.lastRequest.Spec.Token, token) ctx = authenticator.WithAudiences(ctx, tt.reqAuds)
} }
if !reflect.DeepEqual(serv.lastRequest.TypeMeta, expTypeMeta) {
t.Errorf("case %d: Server did not see correct TypeMeta. Got %v, expected %v", serv.response = tt.serverResponse
i, serv.lastRequest.TypeMeta, expTypeMeta) resp, authenticated, err := wh.AuthenticateToken(ctx, token)
} if err != nil {
if authenticated != tt.expectedAuthenticated { t.Fatalf("authentication failed: %v", err)
t.Errorf("case %d: Plugin returned incorrect authentication response. Got %t, expected %t.", }
i, authenticated, tt.expectedAuthenticated) if serv.lastRequest.Spec.Token != token {
} t.Errorf("Server did not see correct token. Got %q, expected %q.",
if resp != nil && tt.expectedUser != nil && !reflect.DeepEqual(resp.User, tt.expectedUser) { serv.lastRequest.Spec.Token, token)
t.Errorf("case %d: Plugin returned incorrect user. Got %#v, expected %#v", }
i, resp.User, tt.expectedUser) if !reflect.DeepEqual(serv.lastRequest.TypeMeta, expTypeMeta) {
} t.Errorf("Server did not see correct TypeMeta. Got %v, expected %v",
serv.lastRequest.TypeMeta, expTypeMeta)
}
if authenticated != tt.expectedAuthenticated {
t.Errorf("Plugin returned incorrect authentication response. Got %t, expected %t.",
authenticated, tt.expectedAuthenticated)
}
if resp != nil && tt.expectedUser != nil && !reflect.DeepEqual(resp.User, tt.expectedUser) {
t.Errorf("Plugin returned incorrect user. Got %#v, expected %#v",
resp.User, tt.expectedUser)
}
if resp != nil && tt.expectedAuds != nil && !reflect.DeepEqual(resp.Audiences, tt.expectedAuds) {
t.Errorf("Plugin returned incorrect audiences. Got %#v, expected %#v",
resp.Audiences, tt.expectedAuds)
}
})
} }
} }
...@@ -440,7 +557,7 @@ func TestWebhookCacheAndRetry(t *testing.T) { ...@@ -440,7 +557,7 @@ func TestWebhookCacheAndRetry(t *testing.T) {
defer s.Close() defer s.Close()
// Create an authenticator that caches successful responses "forever" (100 days). // Create an authenticator that caches successful responses "forever" (100 days).
wh, err := newTokenAuthenticator(s.URL, clientCert, clientKey, caCert, 2400*time.Hour) wh, err := newTokenAuthenticator(s.URL, clientCert, clientKey, caCert, 2400*time.Hour, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
...@@ -85,7 +85,7 @@ func getTestWebhookTokenAuth(serverURL string) (authenticator.Request, error) { ...@@ -85,7 +85,7 @@ func getTestWebhookTokenAuth(serverURL string) (authenticator.Request, error) {
if err := json.NewEncoder(kubecfgFile).Encode(config); err != nil { if err := json.NewEncoder(kubecfgFile).Encode(config); err != nil {
return nil, err return nil, err
} }
webhookTokenAuth, err := webhook.New(kubecfgFile.Name()) webhookTokenAuth, err := webhook.New(kubecfgFile.Name(), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment