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
c83f5804
Unverified
Commit
c83f5804
authored
Oct 08, 2016
by
Jordan Liggitt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Wire kubelet authn/authz
parent
a602ae77
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
340 additions
and
6 deletions
+340
-6
auth.go
cmd/kubelet/app/auth.go
+131
-0
server.go
cmd/kubelet/app/server.go
+25
-2
BUILD
pkg/kubelet/server/BUILD
+5
-1
auth.go
pkg/kubelet/server/auth.go
+77
-0
auth_test.go
pkg/kubelet/server/auth_test.go
+53
-0
server_test.go
pkg/kubelet/server/server_test.go
+49
-3
No files found.
cmd/kubelet/app/auth.go
0 → 100644
View file @
c83f5804
/*
Copyright 2016 The Kubernetes 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
app
import
(
"errors"
"fmt"
"reflect"
"k8s.io/kubernetes/pkg/apis/componentconfig"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/group"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
authenticationclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned"
authorizationclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned"
alwaysallowauthorizer
"k8s.io/kubernetes/pkg/genericapiserver/authorizer"
"k8s.io/kubernetes/pkg/kubelet/server"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/cert"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous"
unionauth
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
webhooktoken
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook"
webhooksar
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook"
)
func
buildAuth
(
nodeName
types
.
NodeName
,
client
internalclientset
.
Interface
,
config
componentconfig
.
KubeletConfiguration
)
(
server
.
AuthInterface
,
error
)
{
// Get clients, if provided
var
(
tokenClient
authenticationclient
.
TokenReviewInterface
sarClient
authorizationclient
.
SubjectAccessReviewInterface
)
if
client
!=
nil
&&
!
reflect
.
ValueOf
(
client
)
.
IsNil
()
{
tokenClient
=
client
.
Authentication
()
.
TokenReviews
()
sarClient
=
client
.
Authorization
()
.
SubjectAccessReviews
()
}
authenticator
,
err
:=
buildAuthn
(
tokenClient
,
config
.
Authentication
)
if
err
!=
nil
{
return
nil
,
err
}
attributes
:=
server
.
NewNodeAuthorizerAttributesGetter
(
nodeName
)
authorizer
,
err
:=
buildAuthz
(
sarClient
,
config
.
Authorization
)
if
err
!=
nil
{
return
nil
,
err
}
return
server
.
NewKubeletAuth
(
authenticator
,
attributes
,
authorizer
),
nil
}
func
buildAuthn
(
client
authenticationclient
.
TokenReviewInterface
,
authn
componentconfig
.
KubeletAuthentication
)
(
authenticator
.
Request
,
error
)
{
authenticators
:=
[]
authenticator
.
Request
{}
// x509 client cert auth
if
len
(
authn
.
X509
.
ClientCAFile
)
>
0
{
clientCAs
,
err
:=
cert
.
NewPool
(
authn
.
X509
.
ClientCAFile
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"unable to load client CA file %s: %v"
,
authn
.
X509
.
ClientCAFile
,
err
)
}
verifyOpts
:=
x509
.
DefaultVerifyOptions
()
verifyOpts
.
Roots
=
clientCAs
authenticators
=
append
(
authenticators
,
x509
.
New
(
verifyOpts
,
x509
.
CommonNameUserConversion
))
}
// bearer token auth that uses authentication.k8s.io TokenReview to determine userinfo
if
authn
.
Webhook
.
Enabled
{
if
client
==
nil
{
return
nil
,
errors
.
New
(
"no client provided, cannot use webhook authentication"
)
}
tokenAuth
,
err
:=
webhooktoken
.
NewFromInterface
(
client
,
authn
.
Webhook
.
CacheTTL
.
Duration
)
if
err
!=
nil
{
return
nil
,
err
}
authenticators
=
append
(
authenticators
,
bearertoken
.
New
(
tokenAuth
))
}
if
len
(
authenticators
)
==
0
{
if
authn
.
Anonymous
.
Enabled
{
return
anonymous
.
NewAuthenticator
(),
nil
}
return
nil
,
errors
.
New
(
"No authentication method configured"
)
}
authenticator
:=
group
.
NewGroupAdder
(
unionauth
.
New
(
authenticators
...
),
[]
string
{
"system:authenticated"
})
if
authn
.
Anonymous
.
Enabled
{
authenticator
=
unionauth
.
NewFailOnError
(
authenticator
,
anonymous
.
NewAuthenticator
())
}
return
authenticator
,
nil
}
func
buildAuthz
(
client
authorizationclient
.
SubjectAccessReviewInterface
,
authz
componentconfig
.
KubeletAuthorization
)
(
authorizer
.
Authorizer
,
error
)
{
switch
authz
.
Mode
{
case
componentconfig
.
KubeletAuthorizationModeAlwaysAllow
:
return
alwaysallowauthorizer
.
NewAlwaysAllowAuthorizer
(),
nil
case
componentconfig
.
KubeletAuthorizationModeWebhook
:
if
client
==
nil
{
return
nil
,
errors
.
New
(
"no client provided, cannot use webhook authorization"
)
}
return
webhooksar
.
NewFromInterface
(
client
,
authz
.
Webhook
.
CacheAuthorizedTTL
.
Duration
,
authz
.
Webhook
.
CacheUnauthorizedTTL
.
Duration
,
)
case
""
:
return
nil
,
fmt
.
Errorf
(
"No authorization mode specified"
)
default
:
return
nil
,
fmt
.
Errorf
(
"Unknown authorization mode %s"
,
authz
.
Mode
)
}
}
cmd/kubelet/app/server.go
View file @
c83f5804
...
...
@@ -62,6 +62,7 @@ import (
kubetypes
"k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/cert"
certutil
"k8s.io/kubernetes/pkg/util/cert"
utilconfig
"k8s.io/kubernetes/pkg/util/config"
"k8s.io/kubernetes/pkg/util/configz"
...
...
@@ -399,6 +400,18 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.KubeletDeps) (err error) {
kubeDeps
.
EventClient
=
eventClient
}
if
kubeDeps
.
Auth
==
nil
{
nodeName
,
err
:=
getNodeName
(
kubeDeps
.
Cloud
,
nodeutil
.
GetHostname
(
s
.
HostnameOverride
))
if
err
!=
nil
{
return
err
}
auth
,
err
:=
buildAuth
(
nodeName
,
kubeDeps
.
KubeClient
,
s
.
KubeletConfiguration
)
if
err
!=
nil
{
return
err
}
kubeDeps
.
Auth
=
auth
}
if
kubeDeps
.
CAdvisorInterface
==
nil
{
kubeDeps
.
CAdvisorInterface
,
err
=
cadvisor
.
New
(
uint
(
s
.
CAdvisorPort
),
s
.
ContainerRuntime
)
if
err
!=
nil
{
...
...
@@ -501,12 +514,22 @@ func InitializeTLS(kc *componentconfig.KubeletConfiguration) (*server.TLSOptions
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion
:
tls
.
VersionTLS12
,
// Populate PeerCertificates in requests, but don't yet reject connections without certificates.
ClientAuth
:
tls
.
RequestClientCert
,
},
CertFile
:
kc
.
TLSCertFile
,
KeyFile
:
kc
.
TLSPrivateKeyFile
,
}
if
len
(
kc
.
Authentication
.
X509
.
ClientCAFile
)
>
0
{
clientCAs
,
err
:=
cert
.
NewPool
(
kc
.
Authentication
.
X509
.
ClientCAFile
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"unable to load client CA file %s: %v"
,
kc
.
Authentication
.
X509
.
ClientCAFile
,
err
)
}
// Specify allowed CAs for client certificates
tlsOptions
.
Config
.
ClientCAs
=
clientCAs
// Populate PeerCertificates in requests, but don't reject connections without verified certificates
tlsOptions
.
Config
.
ClientAuth
=
tls
.
RequestClientCert
}
return
tlsOptions
,
nil
}
...
...
pkg/kubelet/server/BUILD
View file @
c83f5804
...
...
@@ -26,6 +26,7 @@ go_library(
"//pkg/api/validation:go_default_library",
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/authorizer:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/healthz:go_default_library",
"//pkg/httplog:go_default_library",
"//pkg/kubelet/cm:go_default_library",
...
...
@@ -53,7 +54,10 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["server_test.go"],
srcs = [
"auth_test.go",
"server_test.go",
],
library = "go_default_library",
tags = ["automanaged"],
deps = [
...
...
pkg/kubelet/server/auth.go
View file @
c83f5804
...
...
@@ -17,8 +17,14 @@ limitations under the License.
package
server
import
(
"net/http"
"strings"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/types"
)
// KubeletAuth implements AuthInterface
...
...
@@ -35,3 +41,74 @@ type KubeletAuth struct {
func
NewKubeletAuth
(
authenticator
authenticator
.
Request
,
authorizerAttributeGetter
authorizer
.
RequestAttributesGetter
,
authorizer
authorizer
.
Authorizer
)
AuthInterface
{
return
&
KubeletAuth
{
authenticator
,
authorizerAttributeGetter
,
authorizer
}
}
func
NewNodeAuthorizerAttributesGetter
(
nodeName
types
.
NodeName
)
authorizer
.
RequestAttributesGetter
{
return
nodeAuthorizerAttributesGetter
{
nodeName
:
nodeName
}
}
type
nodeAuthorizerAttributesGetter
struct
{
nodeName
types
.
NodeName
}
func
isSubpath
(
subpath
,
path
string
)
bool
{
path
=
strings
.
TrimSuffix
(
path
,
"/"
)
return
subpath
==
path
||
(
strings
.
HasPrefix
(
subpath
,
path
)
&&
subpath
[
len
(
path
)]
==
'/'
)
}
// GetRequestAttributes populates authorizer attributes for the requests to the kubelet API.
// Default attributes are: {apiVersion=v1,verb=<http verb from request>,resource=nodes,name=<node name>,subresource=proxy}
// More specific verb/resource is set for the following request patterns:
// /stats/* => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=stats
// /metrics/* => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=metrics
// /logs/* => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=log
// /spec/* => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=spec
func
(
n
nodeAuthorizerAttributesGetter
)
GetRequestAttributes
(
u
user
.
Info
,
r
*
http
.
Request
)
authorizer
.
Attributes
{
apiVerb
:=
""
switch
r
.
Method
{
case
"POST"
:
apiVerb
=
"create"
case
"GET"
:
apiVerb
=
"get"
case
"PUT"
:
apiVerb
=
"update"
case
"PATCH"
:
apiVerb
=
"patch"
case
"DELETE"
:
apiVerb
=
"delete"
}
requestPath
:=
r
.
URL
.
Path
// Default attributes mirror the API attributes that would allow this access to the kubelet API
attrs
:=
authorizer
.
AttributesRecord
{
User
:
u
,
Verb
:
apiVerb
,
Namespace
:
""
,
APIGroup
:
""
,
APIVersion
:
"v1"
,
Resource
:
"nodes"
,
Subresource
:
"proxy"
,
Name
:
string
(
n
.
nodeName
),
ResourceRequest
:
true
,
Path
:
requestPath
,
}
// Override subresource for specific paths
// This allows subdividing access to the kubelet API
switch
{
case
isSubpath
(
requestPath
,
statsPath
)
:
attrs
.
Subresource
=
"stats"
case
isSubpath
(
requestPath
,
metricsPath
)
:
attrs
.
Subresource
=
"metrics"
case
isSubpath
(
requestPath
,
logsPath
)
:
// "log" to match other log subresources (pods/log, etc)
attrs
.
Subresource
=
"log"
case
isSubpath
(
requestPath
,
specPath
)
:
attrs
.
Subresource
=
"spec"
}
glog
.
V
(
5
)
.
Infof
(
"Node request attributes: attrs=%#v"
,
attrs
)
return
attrs
}
pkg/kubelet/server/auth_test.go
0 → 100644
View file @
c83f5804
/*
Copyright 2016 The Kubernetes 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
server
import
"testing"
func
TestIsSubPath
(
t
*
testing
.
T
)
{
testcases
:=
map
[
string
]
struct
{
subpath
string
path
string
expected
bool
}{
"empty"
:
{
subpath
:
""
,
path
:
""
,
expected
:
true
},
"match 1"
:
{
subpath
:
"foo"
,
path
:
"foo"
,
expected
:
true
},
"match 2"
:
{
subpath
:
"/foo"
,
path
:
"/foo"
,
expected
:
true
},
"match 3"
:
{
subpath
:
"/foo/"
,
path
:
"/foo/"
,
expected
:
true
},
"match 4"
:
{
subpath
:
"/foo/bar"
,
path
:
"/foo/bar"
,
expected
:
true
},
"subpath of root 1"
:
{
subpath
:
"/foo"
,
path
:
"/"
,
expected
:
true
},
"subpath of root 2"
:
{
subpath
:
"/foo/"
,
path
:
"/"
,
expected
:
true
},
"subpath of root 3"
:
{
subpath
:
"/foo/bar"
,
path
:
"/"
,
expected
:
true
},
"subpath of path 1"
:
{
subpath
:
"/foo"
,
path
:
"/foo"
,
expected
:
true
},
"subpath of path 2"
:
{
subpath
:
"/foo/"
,
path
:
"/foo"
,
expected
:
true
},
"subpath of path 3"
:
{
subpath
:
"/foo/bar"
,
path
:
"/foo"
,
expected
:
true
},
"mismatch 1"
:
{
subpath
:
"/foo"
,
path
:
"/bar"
,
expected
:
false
},
"mismatch 2"
:
{
subpath
:
"/foo"
,
path
:
"/foobar"
,
expected
:
false
},
"mismatch 3"
:
{
subpath
:
"/foobar"
,
path
:
"/foo"
,
expected
:
false
},
}
for
k
,
tc
:=
range
testcases
{
result
:=
isSubpath
(
tc
.
subpath
,
tc
.
path
)
if
result
!=
tc
.
expected
{
t
.
Errorf
(
"%s: expected %v, got %v"
,
k
,
tc
.
expected
,
result
)
}
}
}
pkg/kubelet/server/server_test.go
View file @
c83f5804
...
...
@@ -605,10 +605,56 @@ func TestAuthFilters(t *testing.T) {
}
}
methodToAPIVerb
:=
map
[
string
]
string
{
"GET"
:
"get"
,
"POST"
:
"create"
,
"PUT"
:
"update"
}
pathToSubresource
:=
func
(
path
string
)
string
{
switch
{
// Cases for subpaths we expect specific subresources for
case
isSubpath
(
path
,
statsPath
)
:
return
"stats"
case
isSubpath
(
path
,
specPath
)
:
return
"spec"
case
isSubpath
(
path
,
logsPath
)
:
return
"log"
case
isSubpath
(
path
,
metricsPath
)
:
return
"metrics"
// Cases for subpaths we expect to map to the "proxy" subresource
case
isSubpath
(
path
,
"/attach"
),
isSubpath
(
path
,
"/configz"
),
isSubpath
(
path
,
"/containerLogs"
),
isSubpath
(
path
,
"/debug"
),
isSubpath
(
path
,
"/exec"
),
isSubpath
(
path
,
"/healthz"
),
isSubpath
(
path
,
"/pods"
),
isSubpath
(
path
,
"/portForward"
),
isSubpath
(
path
,
"/run"
),
isSubpath
(
path
,
"/runningpods"
)
:
return
"proxy"
default
:
panic
(
fmt
.
Errorf
(
`unexpected kubelet API path %s.
The kubelet API has likely registered a handler for a new path.
If the new path has a use case for partitioned authorization when requested from the kubelet API,
add a specific subresource for it in auth.go#GetRequestAttributes() and in TestAuthFilters().
Otherwise, add it to the expected list of paths that map to the "proxy" subresource in TestAuthFilters().`
,
path
))
}
}
attributesGetter
:=
NewNodeAuthorizerAttributesGetter
(
types
.
NodeName
(
"test"
))
for
_
,
tc
:=
range
testcases
{
var
(
expectedUser
=
&
user
.
DefaultInfo
{
Name
:
"test"
}
expectedAttributes
=
&
authorizer
.
AttributesRecord
{
User
:
expectedUser
}
expectedAttributes
=
authorizer
.
AttributesRecord
{
User
:
expectedUser
,
APIGroup
:
""
,
APIVersion
:
"v1"
,
Verb
:
methodToAPIVerb
[
tc
.
Method
],
Resource
:
"nodes"
,
Name
:
"test"
,
Subresource
:
pathToSubresource
(
tc
.
Path
),
ResourceRequest
:
true
,
Path
:
tc
.
Path
,
}
calledAuthenticate
=
false
calledAuthorize
=
false
...
...
@@ -624,12 +670,12 @@ func TestAuthFilters(t *testing.T) {
if
u
!=
expectedUser
{
t
.
Fatalf
(
"%s: expected user %v, got %v"
,
tc
.
Path
,
expectedUser
,
u
)
}
return
expectedAttributes
return
attributesGetter
.
GetRequestAttributes
(
u
,
req
)
}
fw
.
fakeAuth
.
authorizeFunc
=
func
(
a
authorizer
.
Attributes
)
(
authorized
bool
,
reason
string
,
err
error
)
{
calledAuthorize
=
true
if
a
!=
expectedAttributes
{
t
.
Fatalf
(
"%s: expected attributes
%v, got %
v"
,
tc
.
Path
,
expectedAttributes
,
a
)
t
.
Fatalf
(
"%s: expected attributes
\n\t
%#v
\n
got
\n\t
%#
v"
,
tc
.
Path
,
expectedAttributes
,
a
)
}
return
false
,
""
,
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