mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +00:00
Merge pull request #46009 from timstclair/audit-policy
Automatic merge from submit-queue (batch tested with PRs 45949, 46009, 46320, 46423, 46437) Implement audit policy logic Includes https://github.com/kubernetes/kubernetes/pull/45315#discussion_r117115932 (ignore the first commit) Feature: https://github.com/kubernetes/features/issues/22 Remaining work: - [x] Load the policy into the `server.Config` - [x] Rebase on https://github.com/kubernetes/kubernetes/pull/45315 - [x] Establish shared code for audit api scheme (with https://github.com/kubernetes/kubernetes/pull/45919) - [x] Once https://github.com/kubernetes/kubernetes/pull/45766 is merged, call the policy checker in the audit path /cc @sttts @soltysh @ericchiang @ihmccreery @pweil- @deads2k
This commit is contained in:
commit
ae03f22c65
@ -117,5 +117,6 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
||||
|
||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||
// unintentionally on either side:
|
||||
StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
|
||||
StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
|
||||
genericfeatures.AdvancedAuditing: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
}
|
||||
|
@ -16,8 +16,10 @@ go_library(
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/filters:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -25,8 +25,10 @@ import (
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/server"
|
||||
genericfilters "k8s.io/apiserver/pkg/server/filters"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
@ -35,7 +37,12 @@ import (
|
||||
// InsecureServingInfo *ServingInfo
|
||||
|
||||
func BuildInsecureHandlerChain(apiHandler http.Handler, c *server.Config) http.Handler {
|
||||
handler := genericapifilters.WithAudit(apiHandler, c.RequestContextMapper, c.AuditBackend, c.AuditPolicy, c.LongRunningFunc)
|
||||
handler := apiHandler
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.AdvancedAuditing) {
|
||||
handler = genericapifilters.WithAudit(handler, c.RequestContextMapper, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
|
||||
} else {
|
||||
handler = genericapifilters.WithLegacyAudit(handler, c.RequestContextMapper, c.LegacyAuditWriter)
|
||||
}
|
||||
handler = genericapifilters.WithAuthentication(handler, c.RequestContextMapper, insecureSuperuser{}, nil)
|
||||
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
|
||||
handler = genericfilters.WithPanicRecovery(handler)
|
||||
|
@ -36,11 +36,3 @@ func (a Level) Less(b Level) bool {
|
||||
func (a Level) GreaterOrEqual(b Level) bool {
|
||||
return ordLevel(a) >= ordLevel(b)
|
||||
}
|
||||
|
||||
func NewConstantPolicy(level Level) *Policy {
|
||||
return &Policy{
|
||||
Rules: []PolicyRule{
|
||||
{Level: level},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
27
staging/src/k8s.io/apiserver/pkg/apis/audit/validation/BUILD
Normal file
27
staging/src/k8s.io/apiserver/pkg/apis/audit/validation/BUILD
Normal file
@ -0,0 +1,27 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["validation_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["validation.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
],
|
||||
)
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2017 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 validation
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
)
|
||||
|
||||
func ValidatePolicy(policy *audit.Policy) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
rulePath := field.NewPath("rules")
|
||||
for i, rule := range policy.Rules {
|
||||
allErrs = append(allErrs, validatePolicyRule(rule, rulePath.Index(i))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validatePolicyRule(rule audit.PolicyRule, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
allErrs = append(allErrs, validateLevel(rule.Level, fldPath.Child("level"))...)
|
||||
|
||||
if len(rule.NonResourceURLs) > 0 {
|
||||
if len(rule.Resources) > 0 || len(rule.Namespaces) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var validLevels = []string{
|
||||
string(audit.LevelNone),
|
||||
string(audit.LevelMetadata),
|
||||
string(audit.LevelRequest),
|
||||
string(audit.LevelRequestResponse),
|
||||
}
|
||||
|
||||
func validateLevel(level audit.Level, fldPath *field.Path) field.ErrorList {
|
||||
switch level {
|
||||
case audit.LevelNone, audit.LevelMetadata, audit.LevelRequest, audit.LevelRequestResponse:
|
||||
return nil
|
||||
case "":
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
default:
|
||||
return field.ErrorList{field.NotSupported(fldPath, level, validLevels)}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2017 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 validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
)
|
||||
|
||||
func TestValidatePolicy(t *testing.T) {
|
||||
validRules := []audit.PolicyRule{
|
||||
{ // Defaulting rule
|
||||
Level: audit.LevelMetadata,
|
||||
}, { // Matching non-humans
|
||||
Level: audit.LevelNone,
|
||||
UserGroups: []string{"system:serviceaccounts", "system:nodes"},
|
||||
}, { // Specific request
|
||||
Level: audit.LevelRequestResponse,
|
||||
Verbs: []string{"get"},
|
||||
Resources: []audit.GroupResources{{Resources: []string{"secrets"}}},
|
||||
Namespaces: []string{"kube-system"},
|
||||
}, { // Some non-resource URLs
|
||||
Level: audit.LevelMetadata,
|
||||
UserGroups: []string{"developers"},
|
||||
NonResourceURLs: []string{
|
||||
"/logs*",
|
||||
"/healthz*",
|
||||
"/metrics",
|
||||
},
|
||||
},
|
||||
}
|
||||
successCases := []audit.Policy{}
|
||||
for _, rule := range validRules {
|
||||
successCases = append(successCases, audit.Policy{Rules: []audit.PolicyRule{rule}})
|
||||
}
|
||||
successCases = append(successCases, audit.Policy{}) // Empty policy is valid.
|
||||
successCases = append(successCases, audit.Policy{Rules: validRules}) // Multiple rules.
|
||||
|
||||
for i, policy := range successCases {
|
||||
if errs := ValidatePolicy(&policy); len(errs) != 0 {
|
||||
t.Errorf("[%d] Expected policy %#v to be valid: %v", i, policy, errs)
|
||||
}
|
||||
}
|
||||
|
||||
invalidRules := []audit.PolicyRule{
|
||||
{}, // Empty rule (missing Level)
|
||||
{ // Missing level
|
||||
Verbs: []string{"get"},
|
||||
Resources: []audit.GroupResources{{Resources: []string{"secrets"}}},
|
||||
Namespaces: []string{"kube-system"},
|
||||
}, { // Invalid Level
|
||||
Level: "FooBar",
|
||||
}, { // NonResourceURLs + Namespaces
|
||||
Level: audit.LevelMetadata,
|
||||
Namespaces: []string{"default"},
|
||||
NonResourceURLs: []string{"/logs*"},
|
||||
}, { // NonResourceURLs + ResourceKinds
|
||||
Level: audit.LevelMetadata,
|
||||
Resources: []audit.GroupResources{{Resources: []string{"secrets"}}},
|
||||
NonResourceURLs: []string{"/logs*"},
|
||||
},
|
||||
}
|
||||
errorCases := []audit.Policy{}
|
||||
for _, rule := range invalidRules {
|
||||
errorCases = append(errorCases, audit.Policy{Rules: []audit.PolicyRule{rule}})
|
||||
}
|
||||
errorCases = append(errorCases, audit.Policy{Rules: append(validRules, audit.PolicyRule{})}) // Multiple rules.
|
||||
|
||||
for i, policy := range errorCases {
|
||||
if errs := ValidatePolicy(&policy); len(errs) == 0 {
|
||||
t.Errorf("[%d] Expected policy %#v to be invalid!", i, policy)
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"request.go",
|
||||
"scheme.go",
|
||||
"types.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
@ -20,9 +21,11 @@ go_library(
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
"//vendor/k8s.io/client-go/pkg/apis/authentication/v1:go_default_library",
|
||||
],
|
||||
|
44
staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD
Normal file
44
staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD
Normal file
@ -0,0 +1,44 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"checker_test.go",
|
||||
"reader_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"checker.go",
|
||||
"reader.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/validation:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
],
|
||||
)
|
177
staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go
Normal file
177
staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2017 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 policy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultAuditLevel is the default level to audit at, if no policy rules are matched.
|
||||
DefaultAuditLevel = audit.LevelNone
|
||||
)
|
||||
|
||||
// Checker exposes methods for checking the policy rules.
|
||||
type Checker interface {
|
||||
// Check the audit level for a request with the given authorizer attributes.
|
||||
Level(authorizer.Attributes) audit.Level
|
||||
}
|
||||
|
||||
// NewChecker creates a new policy checker.
|
||||
func NewChecker(policy *audit.Policy) Checker {
|
||||
return &policyChecker{*policy}
|
||||
}
|
||||
|
||||
// FakeChecker creates a checker that returns a constant level for all requests (for testing).
|
||||
func FakeChecker(level audit.Level) Checker {
|
||||
return &fakeChecker{level}
|
||||
}
|
||||
|
||||
type policyChecker struct {
|
||||
audit.Policy
|
||||
}
|
||||
|
||||
func (p *policyChecker) Level(attrs authorizer.Attributes) audit.Level {
|
||||
for _, rule := range p.Rules {
|
||||
if ruleMatches(&rule, attrs) {
|
||||
return rule.Level
|
||||
}
|
||||
}
|
||||
return DefaultAuditLevel
|
||||
}
|
||||
|
||||
// Check whether the rule matches the request attrs.
|
||||
func ruleMatches(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
|
||||
if len(r.Users) > 0 {
|
||||
if !hasString(r.Users, attrs.GetUser().GetName()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(r.UserGroups) > 0 {
|
||||
matched := false
|
||||
for _, group := range attrs.GetUser().GetGroups() {
|
||||
if hasString(r.UserGroups, group) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(r.Verbs) > 0 {
|
||||
if !hasString(r.Verbs, attrs.GetVerb()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Namespaces) > 0 || len(r.Resources) > 0 {
|
||||
return ruleMatchesResource(r, attrs)
|
||||
}
|
||||
|
||||
if len(r.NonResourceURLs) > 0 {
|
||||
return ruleMatchesNonResource(r, attrs)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether the rule's non-resource URLs match the request attrs.
|
||||
func ruleMatchesNonResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
|
||||
if attrs.IsResourceRequest() {
|
||||
return false
|
||||
}
|
||||
|
||||
path := attrs.GetPath()
|
||||
for _, spec := range r.NonResourceURLs {
|
||||
if pathMatches(path, spec) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the path matches the path specification.
|
||||
func pathMatches(path, spec string) bool {
|
||||
// Allow wildcard match
|
||||
if spec == "*" {
|
||||
return true
|
||||
}
|
||||
// Allow exact match
|
||||
if spec == path {
|
||||
return true
|
||||
}
|
||||
// Allow a trailing * subpath match
|
||||
if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the rule's resource fields match the request attrs.
|
||||
func ruleMatchesResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
|
||||
if !attrs.IsResourceRequest() {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(r.Namespaces) > 0 {
|
||||
if !hasString(r.Namespaces, attrs.GetNamespace()) { // Non-namespaced resources use the empty string.
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(r.Resources) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
apiGroup := attrs.GetAPIGroup()
|
||||
resource := attrs.GetResource()
|
||||
for _, gr := range r.Resources {
|
||||
if gr.Group == apiGroup {
|
||||
if len(gr.Resources) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, res := range gr.Resources {
|
||||
if res == resource {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Utility function to check whether a string slice contains a string.
|
||||
func hasString(slice []string, value string) bool {
|
||||
for _, s := range slice {
|
||||
if s == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type fakeChecker struct {
|
||||
level audit.Level
|
||||
}
|
||||
|
||||
func (f *fakeChecker) Level(_ authorizer.Attributes) audit.Level {
|
||||
return f.level
|
||||
}
|
162
staging/src/k8s.io/apiserver/pkg/audit/policy/checker_test.go
Normal file
162
staging/src/k8s.io/apiserver/pkg/audit/policy/checker_test.go
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
Copyright 2017 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 policy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
func TestChecker(t *testing.T) {
|
||||
tim := &user.DefaultInfo{
|
||||
Name: "tim@k8s.io",
|
||||
Groups: []string{"humans", "developers"},
|
||||
}
|
||||
attrs := map[string]authorizer.Attributes{
|
||||
"namespaced": &authorizer.AttributesRecord{
|
||||
User: tim,
|
||||
Verb: "get",
|
||||
Namespace: "default",
|
||||
APIGroup: "", // Core
|
||||
APIVersion: "v1",
|
||||
Resource: "pods",
|
||||
Name: "busybox",
|
||||
ResourceRequest: true,
|
||||
Path: "/api/v1/namespaces/default/pods/busybox",
|
||||
},
|
||||
"cluster": &authorizer.AttributesRecord{
|
||||
User: tim,
|
||||
Verb: "get",
|
||||
APIGroup: "rbac.authorization.k8s.io", // Core
|
||||
APIVersion: "v1beta1",
|
||||
Resource: "clusterroles",
|
||||
Name: "edit",
|
||||
ResourceRequest: true,
|
||||
Path: "/apis/rbac.authorization.k8s.io/v1beta1/clusterroles/edit",
|
||||
},
|
||||
"nonResource": &authorizer.AttributesRecord{
|
||||
User: tim,
|
||||
Verb: "get",
|
||||
ResourceRequest: false,
|
||||
Path: "/logs/kubelet.log",
|
||||
},
|
||||
}
|
||||
|
||||
rules := map[string]audit.PolicyRule{
|
||||
"default": {
|
||||
Level: audit.LevelMetadata,
|
||||
},
|
||||
"create": {
|
||||
Level: audit.LevelRequest,
|
||||
Verbs: []string{"create"},
|
||||
},
|
||||
"tims": {
|
||||
Level: audit.LevelMetadata,
|
||||
Users: []string{"tim@k8s.io"},
|
||||
},
|
||||
"humans": {
|
||||
Level: audit.LevelMetadata,
|
||||
UserGroups: []string{"humans"},
|
||||
},
|
||||
"serviceAccounts": {
|
||||
Level: audit.LevelRequest,
|
||||
UserGroups: []string{"system:serviceaccounts"},
|
||||
},
|
||||
"getPods": {
|
||||
Level: audit.LevelRequestResponse,
|
||||
Verbs: []string{"get"},
|
||||
Resources: []audit.GroupResources{{Resources: []string{"pods"}}},
|
||||
},
|
||||
"getClusterRoles": {
|
||||
Level: audit.LevelRequestResponse,
|
||||
Verbs: []string{"get"},
|
||||
Resources: []audit.GroupResources{{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resources: []string{"clusterroles"},
|
||||
}},
|
||||
Namespaces: []string{""},
|
||||
},
|
||||
"getLogs": {
|
||||
Level: audit.LevelRequestResponse,
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{
|
||||
"/logs*",
|
||||
},
|
||||
},
|
||||
"getMetrics": {
|
||||
Level: audit.LevelRequest,
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{
|
||||
"/metrics",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test := func(req string, expected audit.Level, ruleNames ...string) {
|
||||
policy := audit.Policy{}
|
||||
for _, rule := range ruleNames {
|
||||
require.Contains(t, rules, rule)
|
||||
policy.Rules = append(policy.Rules, rules[rule])
|
||||
}
|
||||
require.Contains(t, attrs, req)
|
||||
actual := NewChecker(&policy).Level(attrs[req])
|
||||
assert.Equal(t, expected, actual, "request:%s rules:%s", req, strings.Join(ruleNames, ","))
|
||||
}
|
||||
|
||||
test("namespaced", audit.LevelMetadata, "default")
|
||||
test("namespaced", audit.LevelNone, "create")
|
||||
test("namespaced", audit.LevelMetadata, "tims")
|
||||
test("namespaced", audit.LevelMetadata, "humans")
|
||||
test("namespaced", audit.LevelNone, "serviceAccounts")
|
||||
test("namespaced", audit.LevelRequestResponse, "getPods")
|
||||
test("namespaced", audit.LevelNone, "getClusterRoles")
|
||||
test("namespaced", audit.LevelNone, "getLogs")
|
||||
test("namespaced", audit.LevelNone, "getMetrics")
|
||||
test("namespaced", audit.LevelMetadata, "getMetrics", "serviceAccounts", "default")
|
||||
test("namespaced", audit.LevelRequestResponse, "getMetrics", "getPods", "default")
|
||||
|
||||
test("cluster", audit.LevelMetadata, "default")
|
||||
test("cluster", audit.LevelNone, "create")
|
||||
test("cluster", audit.LevelMetadata, "tims")
|
||||
test("cluster", audit.LevelMetadata, "humans")
|
||||
test("cluster", audit.LevelNone, "serviceAccounts")
|
||||
test("cluster", audit.LevelNone, "getPods")
|
||||
test("cluster", audit.LevelRequestResponse, "getClusterRoles")
|
||||
test("cluster", audit.LevelNone, "getLogs")
|
||||
test("cluster", audit.LevelNone, "getMetrics")
|
||||
test("cluster", audit.LevelMetadata, "getMetrics", "serviceAccounts", "default")
|
||||
test("cluster", audit.LevelRequestResponse, "getMetrics", "getClusterRoles", "default")
|
||||
|
||||
test("nonResource", audit.LevelMetadata, "default")
|
||||
test("nonResource", audit.LevelNone, "create")
|
||||
test("nonResource", audit.LevelMetadata, "tims")
|
||||
test("nonResource", audit.LevelMetadata, "humans")
|
||||
test("nonResource", audit.LevelNone, "serviceAccounts")
|
||||
test("nonResource", audit.LevelNone, "getPods")
|
||||
test("nonResource", audit.LevelNone, "getClusterRoles")
|
||||
test("nonResource", audit.LevelRequestResponse, "getLogs")
|
||||
test("nonResource", audit.LevelNone, "getMetrics")
|
||||
test("nonResource", audit.LevelMetadata, "getMetrics", "serviceAccounts", "default")
|
||||
test("nonResource", audit.LevelRequestResponse, "getLogs", "getClusterRoles", "default")
|
||||
}
|
57
staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go
Normal file
57
staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2017 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 policy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/apis/audit/validation"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
)
|
||||
|
||||
func LoadPolicyFromFile(filePath string) (*auditinternal.Policy, error) {
|
||||
if filePath == "" {
|
||||
return nil, fmt.Errorf("file path not specified")
|
||||
}
|
||||
policyDef, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file path %q: %+v", filePath, err)
|
||||
}
|
||||
if len(policyDef) == 0 {
|
||||
return nil, fmt.Errorf("file %q was empty", filePath)
|
||||
}
|
||||
policyVersioned := &auditv1alpha1.Policy{}
|
||||
|
||||
decoder := audit.Codecs.UniversalDecoder(auditv1alpha1.SchemeGroupVersion)
|
||||
if err := runtime.DecodeInto(decoder, policyDef, policyVersioned); err != nil {
|
||||
return nil, fmt.Errorf("failed decoding file %q: %v", filePath, err)
|
||||
}
|
||||
|
||||
policy := &auditinternal.Policy{}
|
||||
if err := audit.Scheme.Convert(policyVersioned, policy, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed converting policy: %v", err)
|
||||
}
|
||||
|
||||
if err := validation.ValidatePolicy(policy); err != nil {
|
||||
return nil, err.ToAggregate()
|
||||
}
|
||||
return policy, nil
|
||||
}
|
86
staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go
Normal file
86
staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2017 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 policy
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const policyDef = `
|
||||
rules:
|
||||
- level: None
|
||||
nonResourceURLs:
|
||||
- /healthz*
|
||||
- /version
|
||||
- level: RequestResponse
|
||||
users: ["tim"]
|
||||
userGroups: ["testers", "developers"]
|
||||
verbs: ["patch", "delete", "create"]
|
||||
resources:
|
||||
- group: ""
|
||||
- group: "rbac.authorization.k8s.io"
|
||||
resources: ["clusterroles", "clusterrolebindings"]
|
||||
namespaces: ["default", "kube-system"]
|
||||
- level: Metadata
|
||||
`
|
||||
|
||||
var expectedPolicy = &audit.Policy{
|
||||
Rules: []audit.PolicyRule{{
|
||||
Level: audit.LevelNone,
|
||||
NonResourceURLs: []string{"/healthz*", "/version"},
|
||||
}, {
|
||||
Level: audit.LevelRequestResponse,
|
||||
Users: []string{"tim"},
|
||||
UserGroups: []string{"testers", "developers"},
|
||||
Verbs: []string{"patch", "delete", "create"},
|
||||
Resources: []audit.GroupResources{{}, {
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resources: []string{"clusterroles", "clusterrolebindings"},
|
||||
}},
|
||||
Namespaces: []string{"default", "kube-system"},
|
||||
}, {
|
||||
Level: audit.LevelMetadata,
|
||||
}},
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
// Create a policy file.
|
||||
f, err := ioutil.TempFile("", "policy.yaml")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
_, err = f.WriteString(policyDef)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Close())
|
||||
|
||||
policy, err := LoadPolicyFromFile(f.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, policy.Rules, 3) // Sanity check.
|
||||
if !reflect.DeepEqual(policy, expectedPolicy) {
|
||||
t.Errorf("Unexpected policy! Diff:\n%s", diff.ObjectDiff(policy, expectedPolicy))
|
||||
}
|
||||
}
|
@ -40,21 +40,14 @@ import (
|
||||
authenticationv1 "k8s.io/client-go/pkg/apis/authentication/v1"
|
||||
)
|
||||
|
||||
// NewEventFromRequest generates an audit event for the request.
|
||||
func NewEventFromRequest(req *http.Request, policy *auditinternal.Policy, attribs authorizer.Attributes) (*auditinternal.Event, error) {
|
||||
func NewEventFromRequest(req *http.Request, level auditinternal.Level, attribs authorizer.Attributes) (*auditinternal.Event, error) {
|
||||
ev := &auditinternal.Event{
|
||||
Timestamp: metav1.NewTime(time.Now()),
|
||||
Verb: attribs.GetVerb(),
|
||||
RequestURI: req.URL.RequestURI(),
|
||||
}
|
||||
|
||||
// set the level
|
||||
ev.Level = auditinternal.LevelNone
|
||||
if policy != nil && len(policy.Rules) > 0 {
|
||||
// This is just a hack to get through the test without setting a high level by default.
|
||||
// TODO(audit): add the policy evalutation here
|
||||
ev.Level = policy.Rules[0].Level
|
||||
}
|
||||
ev.Level = level
|
||||
|
||||
// prefer the id from the headers. If not available, create a new one.
|
||||
// TODO(audit): do we want to forbid the header for non-front-proxy users?
|
||||
|
34
staging/src/k8s.io/apiserver/pkg/audit/scheme.go
Normal file
34
staging/src/k8s.io/apiserver/pkg/audit/scheme.go
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
// TODO: Delete this file if we generate a clientset.
|
||||
package audit
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apiserver/pkg/apis/audit/v1alpha1"
|
||||
)
|
||||
|
||||
var Scheme = runtime.NewScheme()
|
||||
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||
|
||||
func init() {
|
||||
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||
v1alpha1.AddToScheme(Scheme)
|
||||
}
|
@ -45,6 +45,7 @@ go_test(
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/example/fuzzer:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/filters:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
|
||||
|
@ -57,6 +57,7 @@ import (
|
||||
examplefuzzer "k8s.io/apiserver/pkg/apis/example/fuzzer"
|
||||
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
auditpolicy "k8s.io/apiserver/pkg/audit/policy"
|
||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
@ -334,7 +335,7 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
|
||||
}
|
||||
}
|
||||
|
||||
handler := genericapifilters.WithAudit(mux, requestContextMapper, auditSink, auditinternal.NewConstantPolicy(auditinternal.LevelRequestResponse), func(r *http.Request, requestInfo *request.RequestInfo) bool {
|
||||
handler := genericapifilters.WithAudit(mux, requestContextMapper, auditSink, auditpolicy.FakeChecker(auditinternal.LevelRequestResponse), func(r *http.Request, requestInfo *request.RequestInfo) bool {
|
||||
// simplified long-running check
|
||||
return requestInfo.Verb == "watch" || requestInfo.Verb == "proxy"
|
||||
})
|
||||
|
@ -24,6 +24,7 @@ go_test(
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
@ -56,6 +57,7 @@ go_library(
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
|
@ -19,16 +19,16 @@ package filters
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/audit/policy"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
@ -49,8 +49,8 @@ import (
|
||||
// 2. the response line containing:
|
||||
// - the unique id from 1
|
||||
// - response code
|
||||
func WithAudit(handler http.Handler, requestContextMapper request.RequestContextMapper, sink audit.Sink, policy *auditinternal.Policy, longRunningCheck request.LongRunningRequestCheck) http.Handler {
|
||||
if sink == nil {
|
||||
func WithAudit(handler http.Handler, requestContextMapper request.RequestContextMapper, sink audit.Sink, policy policy.Checker, longRunningCheck request.LongRunningRequestCheck) http.Handler {
|
||||
if sink == nil || policy == nil {
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@ -67,7 +67,13 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
|
||||
return
|
||||
}
|
||||
|
||||
ev, err := audit.NewEventFromRequest(req, policy, attribs)
|
||||
level := policy.Level(attribs)
|
||||
if level == auditinternal.LevelNone {
|
||||
// Don't audit.
|
||||
handler.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
ev, err := audit.NewEventFromRequest(req, level, attribs)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to complete audit event from request: %v", err))
|
||||
responsewriters.InternalError(w, req, errors.New("failed to update context"))
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit/policy"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
pluginlog "k8s.io/apiserver/plugin/pkg/audit/log"
|
||||
@ -321,9 +322,10 @@ func TestAudit(t *testing.T) {
|
||||
} {
|
||||
var buf bytes.Buffer
|
||||
backend := pluginlog.NewBackend(&buf)
|
||||
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse)
|
||||
handler := WithAudit(http.HandlerFunc(test.handler), &fakeRequestContextMapper{
|
||||
user: &user.DefaultInfo{Name: "admin"},
|
||||
}, backend, auditinternal.NewConstantPolicy(auditinternal.LevelRequestResponse), func(r *http.Request, ri *request.RequestInfo) bool {
|
||||
}, backend, policyChecker, func(r *http.Request, ri *request.RequestInfo) bool {
|
||||
// simplified long-running check
|
||||
return ri.Verb == "watch"
|
||||
})
|
||||
@ -386,7 +388,8 @@ func (*fakeRequestContextMapper) Update(req *http.Request, context request.Conte
|
||||
}
|
||||
|
||||
func TestAuditNoPanicOnNilUser(t *testing.T) {
|
||||
handler := WithAudit(&fakeHTTPHandler{}, &fakeRequestContextMapper{}, &fakeAuditSink{}, auditinternal.NewConstantPolicy(auditinternal.LevelRequestResponse), nil)
|
||||
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse)
|
||||
handler := WithAudit(&fakeHTTPHandler{}, &fakeRequestContextMapper{}, &fakeAuditSink{}, policyChecker, nil)
|
||||
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
@ -33,6 +33,14 @@ const (
|
||||
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
|
||||
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
|
||||
StreamingProxyRedirects utilfeature.Feature = "StreamingProxyRedirects"
|
||||
|
||||
// owner: timstclair
|
||||
// alpha: v1.7
|
||||
//
|
||||
// AdvancedAuditing enables a much more general API auditing pipeline, which includes support for
|
||||
// pluggable output backends and an audit policy specifying how different requests should be
|
||||
// audited.
|
||||
AdvancedAuditing utilfeature.Feature = "AdvancedAuditing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -44,4 +52,5 @@ func init() {
|
||||
// available throughout Kubernetes binaries.
|
||||
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
|
||||
StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
|
||||
AdvancedAuditing: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
}
|
||||
|
@ -77,8 +77,8 @@ go_library(
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/install:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/request/union:go_default_library",
|
||||
@ -92,12 +92,14 @@ go_library(
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/openapi:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/routes:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
goruntime "runtime"
|
||||
@ -38,8 +39,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
auditpolicy "k8s.io/apiserver/pkg/audit/policy"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
|
||||
authenticatorunion "k8s.io/apiserver/pkg/authentication/request/union"
|
||||
@ -51,10 +52,12 @@ import (
|
||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||
apiopenapi "k8s.io/apiserver/pkg/endpoints/openapi"
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic"
|
||||
genericfilters "k8s.io/apiserver/pkg/server/filters"
|
||||
"k8s.io/apiserver/pkg/server/healthz"
|
||||
"k8s.io/apiserver/pkg/server/routes"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/informers"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
@ -101,11 +104,12 @@ type Config struct {
|
||||
|
||||
// Version will enable the /version endpoint if non-nil
|
||||
Version *version.Info
|
||||
// LegacyAuditWriter is the destination for audit logs. If nil, they will not be written.
|
||||
LegacyAuditWriter io.Writer
|
||||
// AuditBackend is where audit events are sent to.
|
||||
AuditBackend audit.Backend
|
||||
// AuditPolicy defines rules which determine the audit level for different requests.
|
||||
AuditPolicy *auditinternal.Policy
|
||||
|
||||
// AuditPolicyChecker makes the decision of whether and how to audit log a request.
|
||||
AuditPolicyChecker auditpolicy.Checker
|
||||
// SupportsBasicAuth indicates that's at least one Authenticator supports basic auth
|
||||
// If this is true, a basic auth challenge is returned on authentication failure
|
||||
// TODO(roberthbailey): Remove once the server no longer supports http basic auth.
|
||||
@ -457,8 +461,11 @@ func (c completedConfig) New(delegationTarget DelegationTarget) (*GenericAPIServ
|
||||
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
|
||||
handler := genericapifilters.WithAuthorization(apiHandler, c.RequestContextMapper, c.Authorizer)
|
||||
handler = genericapifilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer)
|
||||
// TODO(audit): use WithLegacyAudit if feature flag is false
|
||||
handler = genericapifilters.WithAudit(handler, c.RequestContextMapper, c.AuditBackend, c.AuditPolicy, c.LongRunningFunc)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.AdvancedAuditing) {
|
||||
handler = genericapifilters.WithAudit(handler, c.RequestContextMapper, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
|
||||
} else {
|
||||
handler = genericapifilters.WithLegacyAudit(handler, c.RequestContextMapper, c.LegacyAuditWriter)
|
||||
}
|
||||
handler = genericapifilters.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, genericapifilters.Unauthorized(c.SupportsBasicAuth))
|
||||
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
|
||||
handler = genericfilters.WithPanicRecovery(handler)
|
||||
|
@ -54,6 +54,7 @@ go_library(
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
|
@ -17,13 +17,17 @@ limitations under the License.
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
"k8s.io/apiserver/pkg/audit/policy"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/server"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
pluginlog "k8s.io/apiserver/plugin/pkg/audit/log"
|
||||
)
|
||||
|
||||
@ -32,6 +36,8 @@ type AuditLogOptions struct {
|
||||
MaxAge int
|
||||
MaxBackups int
|
||||
MaxSize int
|
||||
|
||||
PolicyFile string
|
||||
}
|
||||
|
||||
func NewAuditLogOptions() *AuditLogOptions {
|
||||
@ -47,9 +53,28 @@ func (o *AuditLogOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
"The maximum number of old audit log files to retain.")
|
||||
fs.IntVar(&o.MaxSize, "audit-log-maxsize", o.MaxSize,
|
||||
"The maximum size in megabytes of the audit log file before it gets rotated.")
|
||||
|
||||
fs.StringVar(&o.PolicyFile, "audit-policy-file", o.PolicyFile,
|
||||
"Path to the file that defines the audit policy configuration. Requires the 'AdvancedAuditing' feature gate."+
|
||||
" With AdvancedAuditing, a profile is required to enable auditing.")
|
||||
}
|
||||
|
||||
func (o *AuditLogOptions) ApplyTo(c *server.Config) error {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.AdvancedAuditing) {
|
||||
if o.PolicyFile != "" {
|
||||
p, err := policy.LoadPolicyFromFile(o.PolicyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.AuditPolicyChecker = policy.NewChecker(p)
|
||||
}
|
||||
} else {
|
||||
if o.PolicyFile != "" {
|
||||
return fmt.Errorf("feature '%s' must be enabled to set an audit policy", features.AdvancedAuditing)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Generalize for alternative audit backends.
|
||||
if len(o.Path) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -63,6 +88,7 @@ func (o *AuditLogOptions) ApplyTo(c *server.Config) error {
|
||||
MaxSize: o.MaxSize,
|
||||
}
|
||||
}
|
||||
c.LegacyAuditWriter = w
|
||||
c.AuditBackend = pluginlog.NewBackend(w)
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user