Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Completing aws registration on spoke #788

Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,16 @@ type ManagedClusterIamRole struct {
}

func (managedClusterIamRole *ManagedClusterIamRole) arn() string {
managedClusterAccountId, _ := getAwsAccountIdAndClusterName(managedClusterIamRole.AwsIrsa.ManagedClusterArn)
managedClusterAccountId, _ := GetAwsAccountIdAndClusterName(managedClusterIamRole.AwsIrsa.ManagedClusterArn)
md5HashUniqueIdentifier := managedClusterIamRole.md5HashSuffix()

//arn:aws:iam::<managed-cluster-account-id>:role/ocm-managed-cluster-<md5-hash-unique-identifier>
return "arn:aws:iam::" + managedClusterAccountId + ":role/ocm-managed-cluster-" + md5HashUniqueIdentifier
}

func (managedClusterIamRole *ManagedClusterIamRole) md5HashSuffix() string {
hubClusterAccountId, hubClusterName := getAwsAccountIdAndClusterName(managedClusterIamRole.AwsIrsa.HubClusterArn)
managedClusterAccountId, managedClusterName := getAwsAccountIdAndClusterName(managedClusterIamRole.AwsIrsa.ManagedClusterArn)
hubClusterAccountId, hubClusterName := GetAwsAccountIdAndClusterName(managedClusterIamRole.AwsIrsa.HubClusterArn)
managedClusterAccountId, managedClusterName := GetAwsAccountIdAndClusterName(managedClusterIamRole.AwsIrsa.ManagedClusterArn)

hash := md5.Sum([]byte(strings.Join([]string{hubClusterAccountId, hubClusterName, managedClusterAccountId, managedClusterName}, "#"))) // #nosec G401
return hex.EncodeToString(hash[:])
Expand Down Expand Up @@ -574,9 +574,14 @@ func serviceAccountName(suffix string, klusterlet *operatorapiv1.Klusterlet) str
return fmt.Sprintf("%s-%s", klusterlet.Name, suffix)
}

func getAwsAccountIdAndClusterName(clusterArn string) (string, string) {
func GetAwsAccountIdAndClusterName(clusterArn string) (string, string) {
clusterStringParts := strings.Split(clusterArn, ":")
clusterName := strings.Split(clusterStringParts[5], "/")[1]
awsAccountId := clusterStringParts[4]
return awsAccountId, clusterName
}

func GetAwsRegion(clusterArn string) string {
clusterStringParts := strings.Split(clusterArn, ":")
return clusterStringParts[3]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we ensure that the slice size is 4?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hub cluster arn shoudl be of the format arn:aws:eks:::cluster/ so the length should be fine.

}
25 changes: 19 additions & 6 deletions pkg/registration/register/aws_irsa/aws.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package aws_irsa

import (
"context"
"fmt"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"

cluster "open-cluster-management.io/api/client/cluster/clientset/versioned"
managedclusterv1client "open-cluster-management.io/api/client/cluster/clientset/versioned/typed/cluster/v1"
managedclusterinformers "open-cluster-management.io/api/client/cluster/informers/externalversions/cluster"
managedclusterv1lister "open-cluster-management.io/api/client/cluster/listers/cluster/v1"
v1 "open-cluster-management.io/api/cluster/v1"
)

type AWSIRSAControl interface {
Expand All @@ -26,23 +33,30 @@ type v1AWSIRSAControl struct {
}

func (v *v1AWSIRSAControl) isApproved(name string) (bool, error) {
// TODO: check if the managedclusuter cr on hub has required condition and is approved
managedcluster, err := v.get(name)
if err != nil {
return false, err
}
v1Managedcluster := managedcluster.(*v1.ManagedCluster)
approved := false

condition := meta.FindStatusCondition(v1Managedcluster.Status.Conditions, v1.ManagedClusterConditionHubAccepted)
if condition != nil {
approved = true
} else {
return false, nil
}
return approved, nil
}

func (v *v1AWSIRSAControl) generateEKSKubeConfig(name string) ([]byte, error) {
// TODO: generate and return kubeconfig
// TODO: generate and return kubeconfig, remove this if not needed
return nil, nil
}

func (v *v1AWSIRSAControl) Informer() cache.SharedIndexInformer {
return v.hubManagedClusterInformer
}

//TODO: Uncomment the below once required in the aws irsa authentication implementation
/*
func (v *v1AWSIRSAControl) get(name string) (metav1.Object, error) {
managedcluster, err := v.hubManagedClusterLister.Get(name)
switch {
Expand All @@ -57,7 +71,6 @@ func (v *v1AWSIRSAControl) get(name string) (metav1.Object, error) {
}
return managedcluster, nil
}
*/

func NewAWSIRSAControl(hubManagedClusterInformer managedclusterinformers.Interface, hubManagedClusterClient cluster.Interface) (AWSIRSAControl, error) {
return &v1AWSIRSAControl{
Expand Down
55 changes: 25 additions & 30 deletions pkg/registration/register/aws_irsa/aws_irsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/cache"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"

clusterv1 "open-cluster-management.io/api/cluster/v1"
operatorv1 "open-cluster-management.io/api/operator/v1"

"open-cluster-management.io/ocm/pkg/operator/operators/klusterlet/controllers/klusterletcontroller"
"open-cluster-management.io/ocm/pkg/registration/register"
)

Expand All @@ -33,20 +33,19 @@ const (
type AWSIRSADriver struct {
name string
managedClusterArn string
hubClusterArn string
managedClusterRoleSuffix string
}

func (c *AWSIRSADriver) Process(
ctx context.Context, controllerName string, secret *corev1.Secret, additionalSecretData map[string][]byte,
recorder events.Recorder, opt any) (*corev1.Secret, *metav1.Condition, error) {
logger := klog.FromContext(ctx)

awsOption, ok := opt.(*AWSOption)
if !ok {
return nil, nil, fmt.Errorf("option type is not correct")
}

// TODO: skip if registration request is not accepted yet, that is the required condition is missing on ManagedCluster CR
isApproved, err := awsOption.AWSIRSAControl.isApproved(c.name)
if err != nil {
return nil, nil, err
Expand All @@ -55,37 +54,31 @@ func (c *AWSIRSADriver) Process(
return nil, nil, nil
}

// TODO: Generate kubeconfig if the request is accepted
eksKubeConfigData, err := awsOption.AWSIRSAControl.generateEKSKubeConfig(c.name)
if err != nil {
return nil, nil, err
}
if len(eksKubeConfigData) == 0 {
return nil, nil, nil
}

secret.Data["kubeconfig"] = eksKubeConfigData
logger.Info("Store kubeconfig into the secret.")

recorder.Eventf("EKSHubKubeconfigCreated", "A new eks hub Kubeconfig for %s is available", controllerName)
//return secret, cond, err
return secret, nil, err
recorder.Eventf("EKSRegistrationRequestApproved", "An EKS registration request is approved for %s", controllerName)
return secret, nil, nil
}

//TODO: Uncomment the below once required in the aws irsa authentication implementation

/*
func (c *AWSIRSADriver) reset() {
c.name = ""
}
*/

func (c *AWSIRSADriver) BuildKubeConfigFromTemplate(kubeConfig *clientcmdapi.Config) *clientcmdapi.Config {
hubClusterAccountId, hubClusterName := klusterletcontroller.GetAwsAccountIdAndClusterName(c.hubClusterArn)
awsRegion := klusterletcontroller.GetAwsRegion(c.hubClusterArn)
kubeConfig.AuthInfos = map[string]*clientcmdapi.AuthInfo{register.DefaultKubeConfigAuth: {
ClientCertificate: TLSCertFile,
ClientKey: TLSKeyFile,
Exec: &clientcmdapi.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1beta1",
Command: "aws",
Args: []string{
"--region",
awsRegion,
"eks",
"get-token",
"--cluster-name",
hubClusterName,
"--output",
"json",
"--role",
fmt.Sprintf("arn:aws:iam::%s:role/ocm-hub-%s", hubClusterAccountId, c.managedClusterRoleSuffix),
},
},
}}

return kubeConfig
}

Expand All @@ -111,9 +104,11 @@ func (c *AWSIRSADriver) ManagedClusterDecorator(cluster *clusterv1.ManagedCluste
return cluster
}

func NewAWSIRSADriver(managedClusterArn string, managedClusterRoleSuffix string) register.RegisterDriver {
func NewAWSIRSADriver(managedClusterArn string, managedClusterRoleSuffix string, hubClusterArn string, name string) register.RegisterDriver {
return &AWSIRSADriver{
managedClusterArn: managedClusterArn,
managedClusterRoleSuffix: managedClusterRoleSuffix,
hubClusterArn: hubClusterArn,
name: name,
}
}
2 changes: 1 addition & 1 deletion pkg/registration/register/aws_irsa/aws_irsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func TestIsHubKubeConfigValidFunc(t *testing.T) {
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
driver := NewAWSIRSADriver("", "")
driver := NewAWSIRSADriver("", "", "", "")
secretOption := register.SecretOption{
ClusterName: c.clusterName,
AgentName: c.agentName,
Expand Down
63 changes: 34 additions & 29 deletions pkg/registration/register/aws_irsa/aws_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package aws_irsa

import (
"fmt"
"reflect"
"testing"

Expand All @@ -12,6 +13,7 @@ import (
managedclusterv1lister "open-cluster-management.io/api/client/cluster/listers/cluster/v1"

"open-cluster-management.io/ocm/pkg/registration/register"
"open-cluster-management.io/ocm/test/integration/util"
)

func TestIsCRApproved(t *testing.T) {
Expand Down Expand Up @@ -49,32 +51,36 @@ func TestBuildKubeconfig(t *testing.T) {
caData []byte
clientCertFile string
clientKeyFile string
AuthInfoExec *clientcmdapi.ExecConfig
}{
{
name: "without proxy",
server: "https://127.0.0.1:6443",
caData: []byte("fake-ca-bundle"),
clientCertFile: "tls.crt",
clientKeyFile: "tls.key",
},
{
name: "with proxy",
server: "https://127.0.0.1:6443",
caData: []byte("fake-ca-bundle-with-proxy-ca"),
proxyURL: "https://127.0.0.1:3129",
clientCertFile: "tls.crt",
clientKeyFile: "tls.key",
name: "without proxy",
server: "https://127.0.0.1:6443",
AuthInfoExec: &clientcmdapi.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1beta1",
Command: "aws",
Args: []string{
"--region",
"us-west-2",
"eks",
"get-token",
"--cluster-name",
"hub-cluster1",
"--output",
"json",
"--role",
fmt.Sprintf("arn:aws:iam::123456789012:role/ocm-hub-%s", ManagedClusterIAMRoleSuffix),
},
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
bootstrapKubeconfig := &clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"default-cluster": {
Server: c.server,
InsecureSkipTLSVerify: false,
CertificateAuthorityData: c.caData,
ProxyURL: c.proxyURL,
Server: c.server,
InsecureSkipTLSVerify: false,
}},
// Define a context that connects the auth info and cluster, and set it as the default
Contexts: map[string]*clientcmdapi.Context{register.DefaultKubeConfigContext: {
Expand All @@ -92,6 +98,8 @@ func TestBuildKubeconfig(t *testing.T) {
}

registerImpl := &AWSIRSADriver{}
registerImpl.hubClusterArn = util.HubClusterArn
registerImpl.managedClusterRoleSuffix = ManagedClusterIAMRoleSuffix
kubeconfig := registerImpl.BuildKubeConfigFromTemplate(bootstrapKubeconfig)
currentContext, ok := kubeconfig.Contexts[kubeconfig.CurrentContext]
if !ok {
Expand All @@ -107,26 +115,23 @@ func TestBuildKubeconfig(t *testing.T) {
t.Errorf("expected server %q, but got %q", c.server, cluster.Server)
}

if cluster.ProxyURL != c.proxyURL {
t.Errorf("expected proxy URL %q, but got %q", c.proxyURL, cluster.ProxyURL)
}

if !reflect.DeepEqual(cluster.CertificateAuthorityData, c.caData) {
t.Errorf("expected ca data %v, but got %v", c.caData, cluster.CertificateAuthorityData)
}

authInfo, ok := kubeconfig.AuthInfos[currentContext.AuthInfo]
if !ok {
t.Errorf("auth info %q not found: %v", currentContext.AuthInfo, kubeconfig)
}

if authInfo.ClientCertificate != c.clientCertFile {
t.Errorf("expected client certificate %q, but got %q", c.clientCertFile, authInfo.ClientCertificate)
if authInfo.Exec.APIVersion != c.AuthInfoExec.APIVersion {
t.Errorf("The value of api version is %s but is expected to be %s", authInfo.Exec.APIVersion, c.AuthInfoExec.APIVersion)
}

if authInfo.ClientKey != c.clientKeyFile {
t.Errorf("expected client key %q, but got %q", c.clientKeyFile, authInfo.ClientKey)
if authInfo.Exec.Command != c.AuthInfoExec.Command {
t.Errorf("Value of AuthInfo.Exec.Command is expected to be %s but got %s", authInfo.Exec.Command, c.AuthInfoExec.Command)
}

if !reflect.DeepEqual(authInfo.Exec.Args, c.AuthInfoExec.Args) {
t.Errorf("Value of AuthInfo.Exec.Args is expected to be %s but got %s", authInfo.Exec.Args, c.AuthInfoExec.Args)
}

})
}
}
12 changes: 2 additions & 10 deletions pkg/registration/register/aws_irsa/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@ package aws_irsa

import (
"fmt"
"strings"

"github.com/openshift/library-go/pkg/controller/factory"
"k8s.io/apimachinery/pkg/api/meta"

addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
hubclusterclientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
managedclusterinformers "open-cluster-management.io/api/client/cluster/informers/externalversions/cluster"
clusterv1 "open-cluster-management.io/api/cluster/v1"

"open-cluster-management.io/ocm/pkg/registration/register"
)

// AWSOption includes options that is used to monitor ManagedClusters
type AWSOption struct {
EventFilterFunc factory.EventFilterFunc

AWSIRSAControl AWSIRSAControl
AWSIRSAControl AWSIRSAControl
}

func NewAWSOption(
Expand All @@ -35,16 +32,11 @@ func NewAWSOption(
}
return &AWSOption{
EventFilterFunc: func(obj interface{}) bool {
// TODO: implement EventFilterFunc and update below
accessor, err := meta.Accessor(obj)
if err != nil {
return false
}
labels := accessor.GetLabels()
// only enqueue csr from a specific managed cluster
if labels[clusterv1.ClusterNameLabelKey] != secretOption.ClusterName {
return false
}

// should not contain addon key
_, ok := labels[addonv1alpha1.AddonLabelKey]
Expand All @@ -53,7 +45,7 @@ func NewAWSOption(
}

// only enqueue csr whose name starts with the cluster name
return strings.HasPrefix(accessor.GetName(), fmt.Sprintf("%s-", secretOption.ClusterName))
return accessor.GetName() == secretOption.ClusterName
},
AWSIRSAControl: awsIrsaControl,
}, nil
Expand Down
Loading
Loading