Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aks-node-controller/helpers/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const (
NetworkPolicyAzure = "azure"
NetworkPolicyCalico = "calico"
LoadBalancerBasic = "basic"
LoadBalancerStandard = "Standard"
LoadBalancerStandard = "standard"
VMSizeStandardDc2s = "Standard_DC2s"
VMSizeStandardDc4s = "Standard_DC4s"
DefaultLinuxUser = "azureuser"
Expand Down
111 changes: 109 additions & 2 deletions aks-node-controller/helpers/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ func Test_getLoadBalancerSKU(t *testing.T) {
{
name: "LoadBalancerSKU Standard",
args: args{
sku: "Standard",
sku: "standard",
},
want: aksnodeconfigv1.LoadBalancerSku_LOAD_BALANCER_SKU_STANDARD,
},
{
name: "LoadBalancerSKU Basic",
args: args{
sku: "Basic",
sku: "basic",
},
want: aksnodeconfigv1.LoadBalancerSku_LOAD_BALANCER_SKU_BASIC,
},
Expand Down Expand Up @@ -299,3 +299,110 @@ func TestIsKubeletServingCertificateRotationEnabled(t *testing.T) {
})
}
}

func TestValidateAndSetLinuxKubeletFlags_RemovesDeprecatedFlags(t *testing.T) {
kubeletFlags := map[string]string{
"--dynamic-config-dir": "/var/lib/kubelet",
"--non-masquerade-cidr": "10.240.0.0/12",
"--cni-bin-dir": "/opt/cni/bin",
"--cni-cache-dir": "/var/lib/cni",
"--cni-conf-dir": "/etc/cni/net.d",
"--docker-endpoint": "npipe:////./pipe/docker_engine",
"--image-pull-progress-deadline": "30m",
"--network-plugin": "cni",
"--network-plugin-mtu": "1500",
"--feature-gates": "",
}

ValidateAndSetLinuxKubeletFlags(kubeletFlags, newTestContainerService("1.27.3"), &datamodel.AgentPoolProfile{})

removedFlags := []string{
"--dynamic-config-dir",
"--non-masquerade-cidr",
"--cni-bin-dir",
"--cni-cache-dir",
"--cni-conf-dir",
"--docker-endpoint",
"--image-pull-progress-deadline",
"--network-plugin",
"--network-plugin-mtu",
}

for _, flag := range removedFlags {
if _, exists := kubeletFlags[flag]; exists {
t.Fatalf("expected flag %s to be removed", flag)
}
}
}

func TestValidateAndSetLinuxKubeletFlags_FeatureGatesByVersion(t *testing.T) {
testCases := []struct {
name string
version string
initialFeatureGates string
rotateServerCerts bool
expectedFeatureGates map[string]bool
}{
{
name: "removes dynamic gate when version >= 1.24",
version: "1.26.0",
initialFeatureGates: "DynamicKubeletConfig=false,OtherFeature=true",
expectedFeatureGates: map[string]bool{
"OtherFeature": true,
},
},
{
name: "adds dynamic and disable accelerator gates for 1.22",
version: "1.22.6",
initialFeatureGates: "FooBar=true",
expectedFeatureGates: map[string]bool{
"FooBar": true,
"DynamicKubeletConfig": false,
"DisableAcceleratorUsageMetrics": false,
},
},
{
name: "does not add dynamic gate before 1.11",
version: "1.10.13",
initialFeatureGates: "",
expectedFeatureGates: map[string]bool{},
},
{
name: "adds rotate kubelet server certificate gate when enabled",
version: "1.28.2",
initialFeatureGates: "",
rotateServerCerts: true,
expectedFeatureGates: map[string]bool{
"RotateKubeletServerCertificate": true,
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
kubeletFlags := map[string]string{
"--feature-gates": tc.initialFeatureGates,
}
if tc.rotateServerCerts {
kubeletFlags["--rotate-server-certificates"] = "true"
}

ValidateAndSetLinuxKubeletFlags(kubeletFlags, newTestContainerService(tc.version), &datamodel.AgentPoolProfile{})

featureGateMap := strKeyValToMapBool(kubeletFlags["--feature-gates"], ",", "=")
if !reflect.DeepEqual(featureGateMap, tc.expectedFeatureGates) {
t.Fatalf("unexpected feature gates: got %v, want %v", featureGateMap, tc.expectedFeatureGates)
}
})
}
}

func newTestContainerService(version string) *datamodel.ContainerService {
return &datamodel.ContainerService{
Properties: &datamodel.Properties{
OrchestratorProfile: &datamodel.OrchestratorProfile{
OrchestratorVersion: version,
},
},
}
}
7 changes: 0 additions & 7 deletions aks-node-controller/parser/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,13 +596,6 @@ func getDisableSSH(v *aksnodeconfigv1.Configuration) bool {
return !v.GetEnableSsh()
}

func getServicePrincipalFileContent(authConfig *aksnodeconfigv1.AuthConfig) string {
if authConfig.GetServicePrincipalSecret() == "" {
return ""
}
return base64.StdEncoding.EncodeToString([]byte(authConfig.GetServicePrincipalSecret()))
}

func getKubeletFlags(kubeletConfig *aksnodeconfigv1.KubeletConfig) string {
return createSortedKeyValuePairs(kubeletConfig.GetKubeletFlags(), " ")
}
Expand Down
2 changes: 1 addition & 1 deletion aks-node-controller/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func getCSEEnv(config *aksnodeconfigv1.Configuration) map[string]string {
"DHCPV6_CONFIG_FILEPATH": getDHCPV6ConfigFilepath(),
"THP_ENABLED": config.GetCustomLinuxOsConfig().GetTransparentHugepageSupport(),
"THP_DEFRAG": config.GetCustomLinuxOsConfig().GetTransparentDefrag(),
"SERVICE_PRINCIPAL_FILE_CONTENT": getServicePrincipalFileContent(config.AuthConfig),
"SERVICE_PRINCIPAL_FILE_CONTENT": config.GetAuthConfig().GetServicePrincipalSecret(),
"KUBELET_CLIENT_CONTENT": config.GetKubeletConfig().GetKubeletClientKey(),
"KUBELET_CLIENT_CERT_CONTENT": config.GetKubeletConfig().GetKubeletClientCertContent(),
"KUBELET_CONFIG_FILE_ENABLED": fmt.Sprintf("%v", config.GetKubeletConfig().GetEnableKubeletConfigFile()),
Expand Down
15 changes: 15 additions & 0 deletions aks-node-controller/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,21 @@ oom_score = -999
}
}

func TestBuildCSECmd_SetsServicePrincipalFileContent(t *testing.T) {
secret := "super-secret-value"
cmd, err := BuildCSECmd(context.TODO(), &aksnodeconfigv1.Configuration{
AuthConfig: &aksnodeconfigv1.AuthConfig{ServicePrincipalSecret: secret},
})
require.NoError(t, err)

vars := environToMap(cmd.Env)
require.Contains(t, vars, "SERVICE_PRINCIPAL_FILE_CONTENT")

// The value should be exactly the secret, without additional base64 encoding.
// Actually the client which passes the secret to aks-node-controller should have base64 encoded it first.
assert.Equal(t, secret, vars["SERVICE_PRINCIPAL_FILE_CONTENT"])
}

func TestAKSNodeConfigCompatibilityFromJsonToCSECommand(t *testing.T) {
tests := []struct {
name string
Expand Down
4 changes: 2 additions & 2 deletions e2e/node_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ func baseTemplateLinux(t testing.TB, location string, k8sVersion string, arch st
},
ServicePrincipalProfile: &datamodel.ServicePrincipalProfile{
ClientID: "msi",
Secret: "**msi**",
Secret: base64.StdEncoding.EncodeToString([]byte("msi")),
},
CertificateProfile: &datamodel.CertificateProfile{},
HostedMasterProfile: &datamodel.HostedMasterProfile{},
Expand Down Expand Up @@ -816,7 +816,7 @@ func baseTemplateWindows(t testing.TB, location string) *datamodel.NodeBootstrap
},
ServicePrincipalProfile: &datamodel.ServicePrincipalProfile{
ClientID: "msi",
Secret: "**msi**",
Secret: base64.StdEncoding.EncodeToString([]byte("msi")),
},
FeatureFlags: &datamodel.FeatureFlags{
EnableWinDSR: true,
Expand Down
47 changes: 46 additions & 1 deletion e2e/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func ValidateLeakedSecrets(ctx context.Context, s *Scenario) {
for _, logFile := range []string{"/var/log/azure/cluster-provision.log", "/var/log/azure/aks-node-controller.log"} {
for _, secretValue := range secrets {
if secretValue != "" {
ValidateFileExcludesContent(ctx, s, logFile, secretValue)
ValidateFileExcludesExactContent(ctx, s, logFile, secretValue)
}
}
}
Expand Down Expand Up @@ -384,20 +384,65 @@ func fileHasContent(ctx context.Context, s *Scenario, fileName string, contents
}
}

func fileHasExactContent(ctx context.Context, s *Scenario, fileName string, contents string) bool {
s.T.Helper()
require.NotEmpty(s.T, contents, "Test setup failure: Can't validate that a file has contents with an empty string. Filename: %s", fileName)
encodedPattern := base64.StdEncoding.EncodeToString([]byte(contents))
if s.IsWindows() {
steps := []string{
"$ErrorActionPreference = \"Stop\"",
fmt.Sprintf("if ( -not ( Test-Path -Path %s ) ) { exit 2 }", fileName),
fmt.Sprintf("$pattern = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('%s'))", encodedPattern),
fmt.Sprintf("$content = Get-Content -Path %s -Raw", fileName),
"$escaped = [regex]::Escape($pattern)",
"if ([regex]::Match($content, \"(?<!\\w)\" + $escaped + \"(?!\\w)\").Success) { exit 0 } else { exit 1 }",
}
execResult := execScriptOnVMForScenario(ctx, s, strings.Join(steps, "\n"))
return execResult.exitCode == "0"
} else {
steps := []string{
"set -ex",
fmt.Sprintf("if [ ! -f %s ]; then exit 2; fi", fileName),
fmt.Sprintf("pattern=$(printf '%%s' '%s' | base64 -d)", encodedPattern),
"escaped=$(printf '%s\n' \"$pattern\" | sed -e 's/[.\\[\\()*?^$+{}|]/\\\\&/g')",
"regex='(^|[^[:alnum:]_])'\"$escaped\"'([^[:alnum:]_]|$)'",
fmt.Sprintf("if sudo grep -Eq \"$regex\" %s; then exit 0; else exit 1; fi", fileName),
}
execResult := execScriptOnVMForScenario(ctx, s, strings.Join(steps, "\n"))
return execResult.exitCode == "0"
}
}

// ValidateFileHasContent passes the test if the specified file contains the specified contents.
// The contents doesn't need to be surrounded by non-word characters.
// E.g.: searching "bcd" in "abcdef" is a match, thus the validation passes.
func ValidateFileHasContent(ctx context.Context, s *Scenario, fileName string, contents string) {
s.T.Helper()
if !fileHasContent(ctx, s, fileName, contents) {
s.T.Fatalf("expected file %s to have contents %q, but it does not", fileName, contents)
}
}

// ValidateFileExcludesContent fails the test if the specified file contains the specified contents.
// The contents doesn't need to be surrounded by non-word characters.
// E.g.: searching "bcd" in "abcdef" is a match, thus the validation fails.
func ValidateFileExcludesContent(ctx context.Context, s *Scenario, fileName string, contents string) {
s.T.Helper()
if fileHasContent(ctx, s, fileName, contents) {
s.T.Fatalf("expected file %s to not have contents %q, but it does", fileName, contents)
}
}

// ValidateFileExcludesExactContent fails the test if the specified file contains the specified contents.
// The contents needs to be surrounded by non-word characters.
// E.g.: searching "bcd" in "abcdef" is not a match, thus the validation passes.
func ValidateFileExcludesExactContent(ctx context.Context, s *Scenario, fileName string, contents string) {
s.T.Helper()
if fileHasExactContent(ctx, s, fileName, contents) {
s.T.Fatalf("expected file %s to not have exact contents %q, but it does", fileName, contents)
}
}

func ServiceCanRestartValidator(ctx context.Context, s *Scenario, serviceName string, restartTimeoutInSeconds int) {
s.T.Helper()
steps := []string{
Expand Down
9 changes: 7 additions & 2 deletions e2e/vmss.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,13 @@ func extractLogsFromVMLinux(ctx context.Context, s *Scenario, vm *ScenarioVM) er
"cluster-provision-cse-output.log": "sudo cat /var/log/azure/cluster-provision-cse-output.log",
"sysctl-out.log": "sudo sysctl -a",
"aks-node-controller.log": "sudo cat /var/log/azure/aks-node-controller.log",
"syslog": "sudo cat /var/log/" + syslogHandle,
"journalctl": "sudo journalctl --boot=0 --no-pager",
"aks-node-controller-config.json": "sudo cat /opt/azure/containers/aks-node-controller-config.json", // Only available in Scriptless.

// Only available in Scriptless. By default, e2e enables aks-node-controller-hack, so this is the actual config used. Only in e2e. Not used in production.
"aks-node-controller-config-hack.json": "sudo cat /opt/azure/containers/aks-node-controller-config-hack.json",
"syslog": "sudo cat /var/log/" + syslogHandle,
"journalctl": "sudo journalctl --boot=0 --no-pager",
"azure.json": "sudo cat /etc/kubernetes/azure.json",
}
if s.SecureTLSBootstrappingEnabled() {
commandList["secure-tls-bootstrap.log"] = "sudo cat /var/log/azure/aks/secure-tls-bootstrap.log"
Expand Down
Loading