mirror of https://github.com/k3s-io/k3s
parent
456ebf5de7
commit
2cc75f0a5a
|
@ -107,16 +107,18 @@ const (
|
||||||
pvVertexType
|
pvVertexType
|
||||||
secretVertexType
|
secretVertexType
|
||||||
vaVertexType
|
vaVertexType
|
||||||
|
serviceAccountVertexType
|
||||||
)
|
)
|
||||||
|
|
||||||
var vertexTypes = map[vertexType]string{
|
var vertexTypes = map[vertexType]string{
|
||||||
configMapVertexType: "configmap",
|
configMapVertexType: "configmap",
|
||||||
nodeVertexType: "node",
|
nodeVertexType: "node",
|
||||||
podVertexType: "pod",
|
podVertexType: "pod",
|
||||||
pvcVertexType: "pvc",
|
pvcVertexType: "pvc",
|
||||||
pvVertexType: "pv",
|
pvVertexType: "pv",
|
||||||
secretVertexType: "secret",
|
secretVertexType: "secret",
|
||||||
vaVertexType: "volumeattachment",
|
vaVertexType: "volumeattachment",
|
||||||
|
serviceAccountVertexType: "serviceAccount",
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be called under a write lock
|
// must be called under a write lock
|
||||||
|
@ -204,6 +206,7 @@ func (g *Graph) deleteVertex_locked(vertexType vertexType, namespace, name strin
|
||||||
// secret -> pod
|
// secret -> pod
|
||||||
// configmap -> pod
|
// configmap -> pod
|
||||||
// pvc -> pod
|
// pvc -> pod
|
||||||
|
// svcacct -> pod
|
||||||
func (g *Graph) AddPod(pod *api.Pod) {
|
func (g *Graph) AddPod(pod *api.Pod) {
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
defer g.lock.Unlock()
|
defer g.lock.Unlock()
|
||||||
|
@ -213,6 +216,14 @@ func (g *Graph) AddPod(pod *api.Pod) {
|
||||||
nodeVertex := g.getOrCreateVertex_locked(nodeVertexType, "", pod.Spec.NodeName)
|
nodeVertex := g.getOrCreateVertex_locked(nodeVertexType, "", pod.Spec.NodeName)
|
||||||
g.graph.SetEdge(newDestinationEdge(podVertex, nodeVertex, nodeVertex))
|
g.graph.SetEdge(newDestinationEdge(podVertex, nodeVertex, nodeVertex))
|
||||||
|
|
||||||
|
// TODO(mikedanese): If the pod doesn't mount the service account secrets,
|
||||||
|
// should the node still get access to the service account?
|
||||||
|
//
|
||||||
|
// ref https://github.com/kubernetes/kubernetes/issues/58790
|
||||||
|
if len(pod.Spec.ServiceAccountName) > 0 {
|
||||||
|
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(serviceAccountVertexType, pod.Namespace, pod.Spec.ServiceAccountName), podVertex, nodeVertex))
|
||||||
|
}
|
||||||
|
|
||||||
podutil.VisitPodSecretNames(pod, func(secret string) bool {
|
podutil.VisitPodSecretNames(pod, func(secret string) bool {
|
||||||
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(secretVertexType, pod.Namespace, secret), podVertex, nodeVertex))
|
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(secretVertexType, pod.Namespace, secret), podVertex, nodeVertex))
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -70,6 +70,7 @@ var (
|
||||||
pvcResource = api.Resource("persistentvolumeclaims")
|
pvcResource = api.Resource("persistentvolumeclaims")
|
||||||
pvResource = api.Resource("persistentvolumes")
|
pvResource = api.Resource("persistentvolumes")
|
||||||
vaResource = storageapi.Resource("volumeattachments")
|
vaResource = storageapi.Resource("volumeattachments")
|
||||||
|
svcAcctResource = api.Resource("serviceaccounts")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
|
func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
@ -106,6 +107,11 @@ func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Deci
|
||||||
return r.authorizeGet(nodeName, vaVertexType, attrs)
|
return r.authorizeGet(nodeName, vaVertexType, attrs)
|
||||||
}
|
}
|
||||||
return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.CSIPersistentVolume), nil
|
return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.CSIPersistentVolume), nil
|
||||||
|
case svcAcctResource:
|
||||||
|
if r.features.Enabled(features.TokenRequest) {
|
||||||
|
return r.authorizeCreateToken(nodeName, serviceAccountVertexType, attrs)
|
||||||
|
}
|
||||||
|
return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.TokenRequest), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +171,31 @@ func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, att
|
||||||
return authorizer.DecisionAllow, "", nil
|
return authorizer.DecisionAllow, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// authorizeCreateToken authorizes "create" requests to serviceaccounts 'token'
|
||||||
|
// subresource of pods running on a node
|
||||||
|
func (r *NodeAuthorizer) authorizeCreateToken(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
if attrs.GetVerb() != "create" || len(attrs.GetName()) == 0 {
|
||||||
|
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
|
||||||
|
return authorizer.DecisionNoOpinion, "can only create tokens for individual service accounts", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if attrs.GetSubresource() != "token" {
|
||||||
|
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
|
||||||
|
return authorizer.DecisionNoOpinion, "can only create token subresource of serviceaccount", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
|
||||||
|
if err != nil {
|
||||||
|
glog.V(2).Infof("NODE DENY: %v", err)
|
||||||
|
return authorizer.DecisionNoOpinion, "no path found to object", nil
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
glog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs)
|
||||||
|
return authorizer.DecisionNoOpinion, "no path found to object", nil
|
||||||
|
}
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
// hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node
|
// hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node
|
||||||
func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, startingNamespace, startingName string) (bool, error) {
|
func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, startingNamespace, startingName string) (bool, error) {
|
||||||
r.graph.lock.RLock()
|
r.graph.lock.RLock()
|
||||||
|
|
|
@ -38,6 +38,8 @@ import (
|
||||||
var (
|
var (
|
||||||
csiEnabledFeature = utilfeature.NewFeatureGate()
|
csiEnabledFeature = utilfeature.NewFeatureGate()
|
||||||
csiDisabledFeature = utilfeature.NewFeatureGate()
|
csiDisabledFeature = utilfeature.NewFeatureGate()
|
||||||
|
trEnabledFeature = utilfeature.NewFeatureGate()
|
||||||
|
trDisabledFeature = utilfeature.NewFeatureGate()
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -47,6 +49,12 @@ func init() {
|
||||||
if err := csiDisabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{features.CSIPersistentVolume: {Default: false}}); err != nil {
|
if err := csiDisabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{features.CSIPersistentVolume: {Default: false}}); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if err := trEnabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{features.TokenRequest: {Default: true}}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := trDisabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{features.TokenRequest: {Default: false}}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorizer(t *testing.T) {
|
func TestAuthorizer(t *testing.T) {
|
||||||
|
@ -152,6 +160,36 @@ func TestAuthorizer(t *testing.T) {
|
||||||
features: csiEnabledFeature,
|
features: csiEnabledFeature,
|
||||||
expect: authorizer.DecisionAllow,
|
expect: authorizer.DecisionAllow,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "allowed svcacct token create - feature enabled",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"},
|
||||||
|
features: trEnabledFeature,
|
||||||
|
expect: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disallowed svcacct token create - serviceaccount not attached to node",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node1", Namespace: "ns0"},
|
||||||
|
features: trEnabledFeature,
|
||||||
|
expect: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disallowed svcacct token create - feature disabled",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"},
|
||||||
|
features: trDisabledFeature,
|
||||||
|
expect: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disallowed svcacct token create - no subresource",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Name: "svcacct0-node0", Namespace: "ns0"},
|
||||||
|
features: trEnabledFeature,
|
||||||
|
expect: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disallowed svcacct token create - non create",
|
||||||
|
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"},
|
||||||
|
features: trEnabledFeature,
|
||||||
|
expect: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
@ -459,6 +497,7 @@ func generate(opts sampleDataOpts) ([]*api.Pod, []*api.PersistentVolume, []*stor
|
||||||
pod.Namespace = fmt.Sprintf("ns%d", p%opts.namespaces)
|
pod.Namespace = fmt.Sprintf("ns%d", p%opts.namespaces)
|
||||||
pod.Name = fmt.Sprintf("pod%d-%s", p, nodeName)
|
pod.Name = fmt.Sprintf("pod%d-%s", p, nodeName)
|
||||||
pod.Spec.NodeName = nodeName
|
pod.Spec.NodeName = nodeName
|
||||||
|
pod.Spec.ServiceAccountName = fmt.Sprintf("svcacct%d-%s", p, nodeName)
|
||||||
|
|
||||||
for i := 0; i < opts.uniqueSecretsPerPod; i++ {
|
for i := 0; i < opts.uniqueSecretsPerPod; i++ {
|
||||||
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
|
pod.Spec.Volumes = append(pod.Spec.Volumes, api.Volume{VolumeSource: api.VolumeSource{
|
||||||
|
|
Loading…
Reference in New Issue