Skip to content

Commit b49add9

Browse files
mprahlopenshift-merge-bot[bot]
authored andcommitted
Use token authentication for the compliance history API
The addon framework's hub kubeconfig is certificate based. This does not allow authentication to the compliance history API using an OpenShift route that handles the TLS. Because of this, token based authentication is required. This uses the hub kubeconfig's token first and if not set, falls back to getting the token from a secret in the cluster namespace on the Hub created by the policy addon controller. Relates: https://issues.redhat.com/browse/ACM-6889 Signed-off-by: mprahl <[email protected]>
1 parent 04163d8 commit b49add9

File tree

4 files changed

+96
-43
lines changed

4 files changed

+96
-43
lines changed

controllers/statussync/policy_status_sync.go

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/runtime/schema"
2929
"k8s.io/apimachinery/pkg/types"
3030
"k8s.io/client-go/dynamic"
31+
"k8s.io/client-go/kubernetes"
3132
"k8s.io/client-go/rest"
3233
"k8s.io/client-go/tools/record"
3334
"k8s.io/client-go/util/workqueue"
@@ -44,7 +45,10 @@ import (
4445
"open-cluster-management.io/governance-policy-framework-addon/controllers/utils"
4546
)
4647

47-
const ControllerName string = "policy-status-sync"
48+
const (
49+
ControllerName string = "policy-status-sync"
50+
hubComplianceAPISAName string = "open-cluster-management-compliance-history-api-recorder"
51+
)
4852

4953
var (
5054
clusterClaimGVR = schema.GroupVersionResource{
@@ -93,6 +97,7 @@ type PolicyReconciler struct {
9397
//+kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=policies/finalizers,verbs=update
9498
//+kubebuilder:rbac:groups=cluster.open-cluster-management.io,resources=clusterclaims,resourceNames=id.k8s.io,verbs=get
9599
//+kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete
100+
//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get,resourceNames="open-cluster-management-compliance-history-api-recorder"
96101
// This is required for the status lease for the addon framework
97102
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list
98103

@@ -552,6 +557,12 @@ func StartComplianceEventsSyncer(
552557
apiURL string,
553558
events workqueue.RateLimitingInterface,
554559
) error {
560+
var hubToken string
561+
562+
if hubCfg.BearerToken != "" {
563+
hubToken = hubCfg.BearerToken
564+
}
565+
555566
managedClient, err := dynamic.NewForConfig(managedCfg)
556567
if err != nil {
557568
return err
@@ -583,42 +594,26 @@ func StartComplianceEventsSyncer(
583594
caCertPool = x509.NewCertPool()
584595
}
585596

586-
// Append the Kubernete API Server CA in case the Service is exposed directly as opposed to using something like the
587-
// OpenShift router.
597+
// Append the Kubernete API Server CAs in case the Hub cluster's ingress CA is not trusted by the system pool.
588598
if hubCfg.CAData != nil {
589-
caCertPool.AppendCertsFromPEM(hubCfg.CAData)
590-
}
591-
592-
httpClient := &http.Client{Timeout: 60 * time.Second}
593-
594-
var usesCertAuth bool
595-
596-
if hubCfg.CertData != nil && hubCfg.KeyData != nil {
597-
log.Info("Using certificate authentication with the compliance API server")
598-
599-
cert, err := tls.X509KeyPair(hubCfg.CertData, hubCfg.KeyData)
600-
if err != nil {
601-
log.Error(err, "Failed to load the hub kubeconfig for certificate authentication on the compliance API")
602-
603-
return err
599+
_ = caCertPool.AppendCertsFromPEM(hubCfg.CAData)
600+
} else if hubCfg.CAFile != "" {
601+
caData, err := os.ReadFile(hubCfg.CAFile)
602+
if err == nil {
603+
log.Info("The hub kubeconfig CA file can't be read. Ignoring it.", "path", hubCfg.CAFile)
604604
}
605605

606-
httpClient.Transport = &http.Transport{
607-
TLSClientConfig: &tls.Config{
608-
MinVersion: tls.VersionTLS12,
609-
Certificates: []tls.Certificate{cert},
610-
RootCAs: caCertPool,
611-
},
612-
}
606+
_ = caCertPool.AppendCertsFromPEM(caData)
607+
}
613608

614-
usesCertAuth = true
615-
} else {
616-
httpClient.Transport = &http.Transport{
609+
httpClient := &http.Client{
610+
Timeout: 60 * time.Second,
611+
Transport: &http.Transport{
617612
TLSClientConfig: &tls.Config{
618613
MinVersion: tls.VersionTLS12,
619614
RootCAs: caCertPool,
620615
},
621-
}
616+
},
622617
}
623618

624619
for {
@@ -671,10 +666,32 @@ func StartComplianceEventsSyncer(
671666

672667
httpRequest.Header.Set("Content-Type", "application/json")
673668

674-
if !usesCertAuth && hubCfg.BearerToken != "" {
675-
httpRequest.Header.Set("Authorization", "Bearer "+hubCfg.BearerToken)
669+
if hubToken == "" {
670+
var err error
671+
672+
hubToken, err = getHubComplianceAPIToken(ctx, hubCfg, clusterName)
673+
if err != nil || hubToken == "" {
674+
var msg string
675+
676+
if err != nil {
677+
msg = err.Error()
678+
} else {
679+
msg = "the token was not set on the secret"
680+
}
681+
682+
log.Info(
683+
"Failed to get the compliance API hub token. Will requeue in 10 seconds.", "error", msg,
684+
)
685+
686+
events.AddAfter(ceUntyped, 10*time.Second)
687+
events.Done(ceUntyped)
688+
689+
continue
690+
}
676691
}
677692

693+
httpRequest.Header.Set("Authorization", "Bearer "+hubToken)
694+
678695
httpResponse, err := httpClient.Do(httpRequest)
679696
if err != nil {
680697
log.Info("Failed to record the compliance event with the compliance API. Will requeue in 10 seconds.")
@@ -705,6 +722,11 @@ func StartComplianceEventsSyncer(
705722
"message", message,
706723
)
707724

725+
if httpResponse.StatusCode == http.StatusUnauthorized || httpResponse.StatusCode == http.StatusForbidden {
726+
// Wipe out the hubToken so that the token is fetched again on the next try.
727+
hubToken = ""
728+
}
729+
708730
events.AddRateLimited(ceUntyped)
709731
events.Done(ceUntyped)
710732

@@ -721,6 +743,24 @@ func StartComplianceEventsSyncer(
721743
}
722744
}
723745

746+
// getHubComplianceAPIToken retrieves the token associated with the service account with compliance history API
747+
// recording permssions in the cluster namespace.
748+
func getHubComplianceAPIToken(ctx context.Context, hubCfg *rest.Config, clusterNamespace string) (string, error) {
749+
client, err := kubernetes.NewForConfig(hubCfg)
750+
if err != nil {
751+
return "", err
752+
}
753+
754+
saTokenSecret, err := client.CoreV1().Secrets(clusterNamespace).Get(
755+
ctx, hubComplianceAPISAName, metav1.GetOptions{},
756+
)
757+
if err != nil {
758+
return "", err
759+
}
760+
761+
return string(saTokenSecret.Data["token"]), nil
762+
}
763+
724764
type historyEvent struct {
725765
policiesv1.ComplianceHistory
726766
eventTime metav1.MicroTime

deploy/operator.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ rules:
110110
- secrets
111111
verbs:
112112
- create
113+
- apiGroups:
114+
- ""
115+
resourceNames:
116+
- open-cluster-management-compliance-history-api-recorder
117+
resources:
118+
- secrets
119+
verbs:
120+
- get
113121
- apiGroups:
114122
- ""
115123
resourceNames:

deploy/rbac/role.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ rules:
8080
- secrets
8181
verbs:
8282
- create
83+
- apiGroups:
84+
- ""
85+
resourceNames:
86+
- open-cluster-management-compliance-history-api-recorder
87+
resources:
88+
- secrets
89+
verbs:
90+
- get
8391
- apiGroups:
8492
- ""
8593
resourceNames:

test/e2e/case23_compliance_api_recording_test.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package e2e
44

55
import (
66
"context"
7-
"crypto/tls"
87
"encoding/json"
98
"io"
109
"net/http"
@@ -15,8 +14,6 @@ import (
1514
. "github.com/onsi/ginkgo/v2"
1615
. "github.com/onsi/gomega"
1716
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18-
"k8s.io/client-go/tools/clientcmd"
19-
certutil "k8s.io/client-go/util/cert"
2017
policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1"
2118
"open-cluster-management.io/governance-policy-propagator/controllers/complianceeventsapi"
2219
"open-cluster-management.io/governance-policy-propagator/test/utils"
@@ -71,22 +68,15 @@ var _ = Describe("Compliance API recording", Ordered, Label("compliance-events-a
7168
BeforeAll(func(ctx context.Context) {
7269
mux := http.NewServeMux()
7370

74-
hubConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigHub)
75-
Expect(err).ToNot(HaveOccurred())
76-
77-
caCertPool, err := certutil.NewPoolFromBytes(hubConfig.CAData)
78-
Expect(err).ToNot(HaveOccurred())
79-
8071
complianceAPIURL := os.Getenv("COMPLIANCE_API_URL")
8172
Expect(complianceAPIURL).ToNot(BeEmpty())
8273

8374
parsedURL, err := url.Parse(complianceAPIURL)
8475
Expect(err).ToNot(HaveOccurred())
8576

8677
server = &http.Server{
87-
Addr: parsedURL.Host,
88-
Handler: mux,
89-
TLSConfig: &tls.Config{ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: caCertPool},
78+
Addr: parsedURL.Host,
79+
Handler: mux,
9080
}
9181

9282
mux.HandleFunc("/api/v1/compliance-events", func(w http.ResponseWriter, r *http.Request) {
@@ -98,6 +88,13 @@ var _ = Describe("Compliance API recording", Ordered, Label("compliance-events-a
9888
return
9989
}
10090

91+
if r.Header.Get("Authorization") == "" {
92+
w.WriteHeader(http.StatusUnauthorized)
93+
_, _ = w.Write([]byte(`{"message": "No token sent"}`))
94+
95+
return
96+
}
97+
10198
body, err := io.ReadAll(r.Body)
10299
if err != nil {
103100
log.Error(err, "error reading request body")

0 commit comments

Comments
 (0)