Skip to content

Commit f06e6e6

Browse files
committed
targetconfigcontroller: make sure extension-apiserver-authentication has necessary annotations
configmap kube-system/extension-apiserver-authentication is created by kube-apiserver, but it doesn't have ownership metadata. This commit updates target config controller to set necessary metadata (ownership and description)
1 parent 0bec046 commit f06e6e6

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# NOTE: This asset defines the required annotations for the live
2+
# kube-system/extension-apiserver-authentication ConfigMap. It is not
3+
# applied directly; the operator reads these annotations and reconciles
4+
# them on the existing ConfigMap created by kube-apiserver.
5+
apiVersion: v1
6+
kind: ConfigMap
7+
metadata:
8+
name: extension-apiserver-authentication
9+
namespace: kube-system
10+
annotations:
11+
"openshift.io/owning-component": "kube-apiserver"
12+
"openshift.io/description": "CA holding the root certificate bundle used to verify client certificates on incoming requests"

pkg/operator/targetconfigcontroller/targetconfigcontroller.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@ func createTargetConfig(ctx context.Context, c TargetConfigController, recorder
237237
errors = append(errors, fmt.Errorf("%q: %v", "configmap/trusted-ca-bundle", err))
238238
}
239239

240+
err = ensureKubeAPIServerExtensionAuthenticationCA(ctx, c.kubeClient.CoreV1(), recorder)
241+
if err != nil {
242+
errors = append(errors, fmt.Errorf("%q: %v", "configmap/extension-apiserver-authentication", err))
243+
}
244+
240245
err = ensureLocalhostRecoverySAToken(ctx, c.kubeClient.CoreV1(), recorder)
241246
if err != nil {
242247
errors = append(errors, fmt.Errorf("%q: %v", "serviceaccount/localhost-recovery-client", err))
@@ -507,6 +512,40 @@ func ensureKubeAPIServerTrustedCA(ctx context.Context, client coreclientv1.CoreV
507512
return err
508513
}
509514

515+
func ensureKubeAPIServerExtensionAuthenticationCA(ctx context.Context, client coreclientv1.CoreV1Interface, recorder events.Recorder) error {
516+
required := resourceread.ReadConfigMapV1OrDie(bindata.MustAsset("assets/kube-apiserver/extension-apiserver-authentication-cm.yaml"))
517+
cmClient := client.ConfigMaps("kube-system")
518+
519+
cm, err := cmClient.Get(ctx, "extension-apiserver-authentication", metav1.GetOptions{})
520+
if err != nil {
521+
// kube-apiserver creates this CM; don't degrade while waiting.
522+
return nil
523+
}
524+
525+
// Ensure that the config map is updated with the required annotations
526+
modified := false
527+
if cm.Annotations == nil {
528+
cm.Annotations = make(map[string]string)
529+
}
530+
531+
for key, expected := range required.Annotations {
532+
if actual, ok := cm.Annotations[key]; !ok || actual != expected {
533+
cm.Annotations[key] = expected
534+
modified = true
535+
}
536+
}
537+
538+
if modified {
539+
if _, err := cmClient.Update(ctx, cm, metav1.UpdateOptions{}); err != nil {
540+
recorder.Warningf("AnnotationUpdateFailed", "Failed to update annotations on configmap kube-system/extension-apiserver-authentication: %v", err)
541+
return err
542+
}
543+
return nil
544+
}
545+
546+
return nil
547+
}
548+
510549
func ensureLocalhostRecoverySAToken(ctx context.Context, client coreclientv1.CoreV1Interface, recorder events.Recorder) error {
511550
requiredSA := resourceread.ReadServiceAccountV1OrDie(bindata.MustAsset("assets/kube-apiserver/localhost-recovery-sa.yaml"))
512551
requiredToken := resourceread.ReadSecretV1OrDie(bindata.MustAsset("assets/kube-apiserver/localhost-recovery-token.yaml"))

pkg/operator/targetconfigcontroller/targetconfigcontroller_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ import (
2626
"k8s.io/utils/clock"
2727

2828
"github.com/ghodss/yaml"
29+
"github.com/google/go-cmp/cmp"
2930
"github.com/openshift/api/annotations"
3031
kubecontrolplanev1 "github.com/openshift/api/kubecontrolplane/v1"
3132
operatorv1 "github.com/openshift/api/operator/v1"
33+
"github.com/openshift/cluster-kube-apiserver-operator/bindata"
3234
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/operatorclient"
3335
"github.com/openshift/library-go/pkg/operator/events"
3436
"github.com/openshift/library-go/pkg/operator/resource/resourcemerge"
37+
"github.com/openshift/library-go/pkg/operator/resource/resourceread"
3538
"github.com/stretchr/testify/require"
39+
clientgotesting "k8s.io/client-go/testing"
3640
)
3741

3842
var codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
@@ -1217,3 +1221,166 @@ func generateTemporaryCertificate() (certPEM []byte, err error) {
12171221

12181222
return certPEM, nil
12191223
}
1224+
1225+
// TestEnsureKubeAPIServerExtensionAuthenticationCA tests the behavior of ensureKubeAPIServerExtensionAuthenticationCA
1226+
func TestEnsureKubeAPIServerExtensionAuthenticationCA(t *testing.T) {
1227+
ctx := context.Background()
1228+
recorder := events.NewInMemoryRecorder("test", clock.RealClock{})
1229+
1230+
t.Run("configmap not found (Get error)", func(t *testing.T) {
1231+
// Create a fake client with no configmap in kube-system
1232+
client := fake.NewSimpleClientset()
1233+
err := ensureKubeAPIServerExtensionAuthenticationCA(ctx, client.CoreV1(), recorder)
1234+
if err != nil {
1235+
t.Fatalf("expected nil error when configmap is missing, got: %v", err)
1236+
}
1237+
})
1238+
1239+
t.Run("configmap exists but missing annotations, update succeeds", func(t *testing.T) {
1240+
// Create a configmap without annotations
1241+
cm := &corev1.ConfigMap{
1242+
ObjectMeta: metav1.ObjectMeta{
1243+
Name: "extension-apiserver-authentication",
1244+
Namespace: "kube-system",
1245+
},
1246+
}
1247+
client := fake.NewSimpleClientset(cm)
1248+
err := ensureKubeAPIServerExtensionAuthenticationCA(ctx, client.CoreV1(), recorder)
1249+
if err != nil {
1250+
t.Fatalf("expected nil error after update, got: %v", err)
1251+
}
1252+
updatedCM, err := client.CoreV1().ConfigMaps("kube-system").Get(ctx, "extension-apiserver-authentication", metav1.GetOptions{})
1253+
if err != nil {
1254+
t.Fatalf("failed to get updated configmap: %v", err)
1255+
}
1256+
if updatedCM.Annotations == nil || updatedCM.Annotations[annotations.OpenShiftComponent] != "kube-apiserver" {
1257+
t.Fatalf("expected annotation not set, got: %v", updatedCM.Annotations)
1258+
}
1259+
})
1260+
1261+
t.Run("configmap exists with correct annotations, no update needed", func(t *testing.T) {
1262+
required := resourceread.ReadConfigMapV1OrDie(bindata.MustAsset("assets/kube-apiserver/extension-apiserver-authentication-cm.yaml"))
1263+
1264+
// Create a configmap with the expected annotation already present
1265+
cm := &corev1.ConfigMap{
1266+
ObjectMeta: metav1.ObjectMeta{
1267+
Name: "extension-apiserver-authentication",
1268+
Namespace: "kube-system",
1269+
Annotations: required.Annotations,
1270+
},
1271+
}
1272+
client := fake.NewSimpleClientset(cm)
1273+
err := ensureKubeAPIServerExtensionAuthenticationCA(ctx, client.CoreV1(), recorder)
1274+
if err != nil {
1275+
t.Fatalf("expected nil error when annotations are already correct, got: %v", err)
1276+
}
1277+
1278+
// Check that client only did one action)
1279+
if len(client.Actions()) != 1 {
1280+
t.Fatalf("expected one action, got: %v", client.Actions())
1281+
}
1282+
action := client.Actions()[0]
1283+
if action.GetVerb() != "get" {
1284+
t.Fatalf("expected get action, got: %v", action)
1285+
}
1286+
getAction := action.(clientgotesting.GetAction)
1287+
if getAction.GetName() != "extension-apiserver-authentication" {
1288+
t.Fatalf("expected get action for configmap 'extension-apiserver-authentication', got: %v", getAction)
1289+
}
1290+
if getAction.GetNamespace() != "kube-system" {
1291+
t.Fatalf("expected get action for namespace 'kube-system', got: %v", getAction)
1292+
}
1293+
})
1294+
1295+
t.Run("update failure propagates error", func(t *testing.T) {
1296+
// Create a configmap without annotations
1297+
cm := &corev1.ConfigMap{
1298+
ObjectMeta: metav1.ObjectMeta{
1299+
Name: "extension-apiserver-authentication",
1300+
Namespace: "kube-system",
1301+
},
1302+
}
1303+
client := fake.NewSimpleClientset(cm)
1304+
1305+
// Inject reactor to simulate update failure
1306+
client.Fake.PrependReactor("update", "configmaps", func(action clientgotesting.Action) (bool, runtime.Object, error) {
1307+
return true, nil, fmt.Errorf("simulated update failure")
1308+
})
1309+
1310+
err := ensureKubeAPIServerExtensionAuthenticationCA(ctx, client.CoreV1(), recorder)
1311+
if err == nil || !strings.Contains(err.Error(), "simulated update failure") {
1312+
t.Fatalf("expected update failure error, got: %v", err)
1313+
}
1314+
})
1315+
1316+
t.Run("unrelated annotations are not removed", func(t *testing.T) {
1317+
unrelatedAnnotations := map[string]string{
1318+
"unrelated.annotation/key1": "value1",
1319+
"unrelated.annotation/key2": "value2",
1320+
}
1321+
1322+
// Create a configmap with unrelated annotations
1323+
cm := &corev1.ConfigMap{
1324+
ObjectMeta: metav1.ObjectMeta{
1325+
Name: "extension-apiserver-authentication",
1326+
Namespace: "kube-system",
1327+
Annotations: unrelatedAnnotations,
1328+
},
1329+
}
1330+
client := fake.NewSimpleClientset(cm)
1331+
1332+
err := ensureKubeAPIServerExtensionAuthenticationCA(ctx, client.CoreV1(), recorder)
1333+
if err != nil {
1334+
t.Fatalf("expected nil error, got: %v", err)
1335+
}
1336+
1337+
updatedCM, err := client.CoreV1().ConfigMaps("kube-system").Get(ctx, "extension-apiserver-authentication", metav1.GetOptions{})
1338+
if err != nil {
1339+
t.Fatalf("failed to get updated configmap: %v", err)
1340+
}
1341+
1342+
required := resourceread.ReadConfigMapV1OrDie(bindata.MustAsset("assets/kube-apiserver/extension-apiserver-authentication-cm.yaml"))
1343+
1344+
expectedAnnotations := map[string]string{}
1345+
for k, v := range required.Annotations {
1346+
expectedAnnotations[k] = v
1347+
}
1348+
for k, v := range unrelatedAnnotations {
1349+
expectedAnnotations[k] = v
1350+
}
1351+
1352+
diff := cmp.Diff(expectedAnnotations, updatedCM.Annotations)
1353+
if diff != "" {
1354+
t.Fatalf("expected annotations to match, but got diff:\n%s", diff)
1355+
}
1356+
})
1357+
1358+
t.Run("configmap exists with incorrect OpenShiftComponent annotation, update succeeds", func(t *testing.T) {
1359+
// Create a configmap with an incorrect OpenShiftComponent annotation
1360+
cm := &corev1.ConfigMap{
1361+
ObjectMeta: metav1.ObjectMeta{
1362+
Name: "extension-apiserver-authentication",
1363+
Namespace: "kube-system",
1364+
Annotations: map[string]string{
1365+
annotations.OpenShiftComponent: "incorrect-value",
1366+
},
1367+
},
1368+
}
1369+
client := fake.NewSimpleClientset(cm)
1370+
1371+
err := ensureKubeAPIServerExtensionAuthenticationCA(ctx, client.CoreV1(), recorder)
1372+
if err != nil {
1373+
t.Fatalf("expected nil error, got: %v", err)
1374+
}
1375+
1376+
updatedCM, err := client.CoreV1().ConfigMaps("kube-system").Get(ctx, "extension-apiserver-authentication", metav1.GetOptions{})
1377+
if err != nil {
1378+
t.Fatalf("failed to get updated configmap: %v", err)
1379+
}
1380+
1381+
// Verify the OpenShiftComponent annotation is updated to the correct value
1382+
if updatedCM.Annotations == nil || updatedCM.Annotations[annotations.OpenShiftComponent] != "kube-apiserver" {
1383+
t.Errorf("expected annotation %s=kube-apiserver, got: %v", annotations.OpenShiftComponent, updatedCM.Annotations)
1384+
}
1385+
})
1386+
}

0 commit comments

Comments
 (0)