k8s.io/apiserver@v0.31.1/pkg/server/options/audit_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package options 18 19 import ( 20 stdjson "encoding/json" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "testing" 26 27 "github.com/spf13/pflag" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 "gopkg.in/natefinch/lumberjack.v2" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" 33 "k8s.io/apiserver/pkg/server" 34 v1 "k8s.io/client-go/tools/clientcmd/api/v1" 35 ) 36 37 func TestAuditValidOptions(t *testing.T) { 38 tmpDir := t.TempDir() 39 auditPath := filepath.Join(tmpDir, "audit") 40 41 webhookConfig := makeTmpWebhookConfig(t) 42 defer os.Remove(webhookConfig) 43 44 policy := makeTmpPolicy(t) 45 defer os.Remove(policy) 46 47 testCases := []struct { 48 name string 49 options func() *AuditOptions 50 expected string 51 }{{ 52 name: "default", 53 options: NewAuditOptions, 54 }, { 55 name: "default log", 56 options: func() *AuditOptions { 57 o := NewAuditOptions() 58 o.LogOptions.Path = auditPath 59 o.PolicyFile = policy 60 return o 61 }, 62 expected: "ignoreErrors<log>", 63 }, { 64 name: "stdout log", 65 options: func() *AuditOptions { 66 o := NewAuditOptions() 67 o.LogOptions.Path = "-" 68 o.PolicyFile = policy 69 return o 70 }, 71 expected: "ignoreErrors<log>", 72 }, { 73 name: "create audit log path dir", 74 options: func() *AuditOptions { 75 o := NewAuditOptions() 76 o.LogOptions.Path = filepath.Join(tmpDir, "non-existing-dir1", "non-existing-dir2", "audit") 77 o.PolicyFile = policy 78 return o 79 }, 80 expected: "ignoreErrors<log>", 81 }, { 82 name: "default log no policy", 83 options: func() *AuditOptions { 84 o := NewAuditOptions() 85 o.LogOptions.Path = auditPath 86 return o 87 }, 88 expected: "", 89 }, { 90 name: "default webhook", 91 options: func() *AuditOptions { 92 o := NewAuditOptions() 93 o.WebhookOptions.ConfigFile = webhookConfig 94 o.PolicyFile = policy 95 return o 96 }, 97 expected: "buffered<webhook>", 98 }, { 99 name: "default webhook no policy", 100 options: func() *AuditOptions { 101 o := NewAuditOptions() 102 o.WebhookOptions.ConfigFile = webhookConfig 103 return o 104 }, 105 expected: "", 106 }, { 107 name: "strict webhook", 108 options: func() *AuditOptions { 109 o := NewAuditOptions() 110 o.WebhookOptions.ConfigFile = webhookConfig 111 o.WebhookOptions.BatchOptions.Mode = ModeBlockingStrict 112 o.PolicyFile = policy 113 return o 114 }, 115 expected: "webhook", 116 }, { 117 name: "default union", 118 options: func() *AuditOptions { 119 o := NewAuditOptions() 120 o.LogOptions.Path = auditPath 121 o.WebhookOptions.ConfigFile = webhookConfig 122 o.PolicyFile = policy 123 return o 124 }, 125 expected: "union[ignoreErrors<log>,buffered<webhook>]", 126 }, { 127 name: "custom", 128 options: func() *AuditOptions { 129 o := NewAuditOptions() 130 o.LogOptions.BatchOptions.Mode = ModeBatch 131 o.LogOptions.Path = auditPath 132 o.WebhookOptions.BatchOptions.Mode = ModeBlocking 133 o.WebhookOptions.ConfigFile = webhookConfig 134 o.PolicyFile = policy 135 return o 136 }, 137 expected: "union[buffered<log>,ignoreErrors<webhook>]", 138 }, { 139 name: "default webhook with truncating", 140 options: func() *AuditOptions { 141 o := NewAuditOptions() 142 o.WebhookOptions.ConfigFile = webhookConfig 143 o.WebhookOptions.TruncateOptions.Enabled = true 144 o.PolicyFile = policy 145 return o 146 }, 147 expected: "truncate<buffered<webhook>>", 148 }, 149 } 150 for _, tc := range testCases { 151 t.Run(tc.name, func(t *testing.T) { 152 options := tc.options() 153 require.NotNil(t, options) 154 155 // Verify flags don't change defaults. 156 fs := pflag.NewFlagSet("Test", pflag.PanicOnError) 157 options.AddFlags(fs) 158 require.NoError(t, fs.Parse(nil)) 159 assert.Equal(t, tc.options(), options, "Flag defaults should match default options.") 160 161 assert.Empty(t, options.Validate(), "Options should be valid.") 162 config := &server.Config{} 163 require.NoError(t, options.ApplyTo(config)) 164 if tc.expected == "" { 165 assert.Nil(t, config.AuditBackend) 166 } else { 167 assert.Equal(t, tc.expected, fmt.Sprintf("%s", config.AuditBackend)) 168 } 169 170 w, err := options.LogOptions.getWriter() 171 require.NoError(t, err, "Writer creation should not fail.") 172 173 // Don't check writer if logging is disabled. 174 if w == nil { 175 return 176 } 177 178 if options.LogOptions.Path == "-" { 179 assert.Equal(t, os.Stdout, w) 180 assert.NoFileExists(t, options.LogOptions.Path) 181 } else { 182 assert.IsType(t, (*lumberjack.Logger)(nil), w) 183 assert.FileExists(t, options.LogOptions.Path) 184 } 185 }) 186 } 187 } 188 189 func TestAuditInvalidOptions(t *testing.T) { 190 tmpDir := t.TempDir() 191 auditPath := filepath.Join(tmpDir, "audit") 192 193 testCases := []struct { 194 name string 195 options func() *AuditOptions 196 }{{ 197 name: "invalid log format", 198 options: func() *AuditOptions { 199 o := NewAuditOptions() 200 o.LogOptions.Path = auditPath 201 o.LogOptions.Format = "foo" 202 return o 203 }, 204 }, { 205 name: "invalid log mode", 206 options: func() *AuditOptions { 207 o := NewAuditOptions() 208 o.LogOptions.Path = auditPath 209 o.LogOptions.BatchOptions.Mode = "foo" 210 return o 211 }, 212 }, { 213 name: "invalid log buffer size", 214 options: func() *AuditOptions { 215 o := NewAuditOptions() 216 o.LogOptions.Path = auditPath 217 o.LogOptions.BatchOptions.Mode = "batch" 218 o.LogOptions.BatchOptions.BatchConfig.BufferSize = -3 219 return o 220 }, 221 }, { 222 name: "invalid webhook mode", 223 options: func() *AuditOptions { 224 o := NewAuditOptions() 225 o.WebhookOptions.ConfigFile = auditPath 226 o.WebhookOptions.BatchOptions.Mode = "foo" 227 return o 228 }, 229 }, { 230 name: "invalid webhook buffer throttle qps", 231 options: func() *AuditOptions { 232 o := NewAuditOptions() 233 o.WebhookOptions.ConfigFile = auditPath 234 o.WebhookOptions.BatchOptions.Mode = "batch" 235 o.WebhookOptions.BatchOptions.BatchConfig.ThrottleQPS = -1 236 return o 237 }, 238 }, { 239 name: "invalid webhook truncate max event size", 240 options: func() *AuditOptions { 241 o := NewAuditOptions() 242 o.WebhookOptions.ConfigFile = auditPath 243 o.WebhookOptions.TruncateOptions.Enabled = true 244 o.WebhookOptions.TruncateOptions.TruncateConfig.MaxEventSize = -1 245 return o 246 }, 247 }, { 248 name: "invalid webhook truncate max batch size", 249 options: func() *AuditOptions { 250 o := NewAuditOptions() 251 o.WebhookOptions.ConfigFile = auditPath 252 o.WebhookOptions.TruncateOptions.Enabled = true 253 o.WebhookOptions.TruncateOptions.TruncateConfig.MaxEventSize = 2 254 o.WebhookOptions.TruncateOptions.TruncateConfig.MaxBatchSize = 1 255 return o 256 }, 257 }, 258 } 259 for _, tc := range testCases { 260 t.Run(tc.name, func(t *testing.T) { 261 options := tc.options() 262 require.NotNil(t, options) 263 assert.NotEmpty(t, options.Validate(), "Options should be invalid.") 264 }) 265 } 266 } 267 268 func makeTmpWebhookConfig(t *testing.T) string { 269 config := v1.Config{ 270 Clusters: []v1.NamedCluster{ 271 {Cluster: v1.Cluster{Server: "localhost", InsecureSkipTLSVerify: true}}, 272 }, 273 } 274 f, err := ioutil.TempFile("", "k8s_audit_webhook_test_") 275 require.NoError(t, err, "creating temp file") 276 require.NoError(t, stdjson.NewEncoder(f).Encode(config), "writing webhook kubeconfig") 277 require.NoError(t, f.Close()) 278 return f.Name() 279 } 280 281 func makeTmpPolicy(t *testing.T) string { 282 pol := auditv1.Policy{ 283 TypeMeta: metav1.TypeMeta{ 284 APIVersion: "audit.k8s.io/v1", 285 }, 286 Rules: []auditv1.PolicyRule{ 287 { 288 Level: auditv1.LevelRequestResponse, 289 }, 290 }, 291 } 292 f, err := ioutil.TempFile("", "k8s_audit_policy_test_") 293 require.NoError(t, err, "creating temp file") 294 require.NoError(t, stdjson.NewEncoder(f).Encode(pol), "writing policy file") 295 require.NoError(t, f.Close()) 296 return f.Name() 297 }