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  }