Skip to content

Commit

Permalink
Read ias secret (#11)
Browse files Browse the repository at this point in the history
* Load Ias Credentials per Reconciliation

* Load IAS Credentials Namespace and Name cfom ENV vars

* Implement ReadCredentials Unit Tests

* Return default IAS creds secret namespace and name

* Adapt Integration Tests for Code Changes

* Use go 1.20 go golint GH action
  • Loading branch information
muralov authored Apr 27, 2023
1 parent fffd795 commit 878c11f
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.19'
go-version: '1.20'
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.19 as builder
FROM eu.gcr.io/kyma-project/external/golang:1.20.2-alpine3.17 as builder
ARG TARGETOS
ARG TARGETARCH

Expand Down
13 changes: 1 addition & 12 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package main
import (
"flag"
"github.com/kyma-project/eventing-auth-manager/controllers"
"github.com/kyma-project/eventing-auth-manager/internal/ias"
"os"
"time"

Expand Down Expand Up @@ -96,17 +95,7 @@ func main() {
os.Exit(1)
}

// TODO: Replace dummy values with values from secret
url := os.Getenv("TEST_EVENTING_AUTH_IAS_URL")
user := os.Getenv("TEST_EVENTING_AUTH_IAS_USER")
pw := os.Getenv("TEST_EVENTING_AUTH_IAS_PASSWORD")
iasClient, err := ias.NewIasClient(url, user, pw)
if err != nil {
setupLog.Error(err, "unable to create ias client", "controller", "EventingAuth")
os.Exit(1)
}

reconciler := controllers.NewEventingAuthReconciler(mgr.GetClient(), mgr.GetScheme(), iasClient, requeueAfterError, requeueAfter)
reconciler := controllers.NewEventingAuthReconciler(mgr.GetClient(), mgr.GetScheme(), requeueAfterError, requeueAfter)

if err = reconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "EventingAuth")
Expand Down
66 changes: 54 additions & 12 deletions controllers/eventingauth_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
"fmt"
"github.com/kyma-project/eventing-auth-manager/internal/ias"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"os"
"reflect"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
Expand All @@ -34,25 +36,28 @@ import (
)

const (
applicationSecretName = "eventing-auth-application"
applicationSecretNamespace = "kyma-system"
eventingAuthFinalizerName = "eventingauth.operator.kyma-project.io/finalizer"
applicationSecretName = "eventing-auth-application"
applicationSecretNamespace = "kyma-system"
eventingAuthFinalizerName = "eventingauth.operator.kyma-project.io/finalizer"
IasCredsSecretNamespace string = "IAS_CREDS_SECRET_NAMESPACE"
IasCredsSecretName string = "IAS_CREDS_SECRET_NAME"
defaultIasCredsNamespaceName string = "kcp-system"
defaultIasCredsSecretName string = "eventing-auth-ias-creds"
)

// eventingAuthReconciler reconciles a EventingAuth object
type eventingAuthReconciler struct {
client.Client
Scheme *runtime.Scheme
IasClient ias.Client
errorRequeuePeriod time.Duration
defaultRequeuePeriod time.Duration
iasClient ias.Client
}

func NewEventingAuthReconciler(c client.Client, s *runtime.Scheme, ias ias.Client, errorRequeuePeriod time.Duration, defaultRequeuePeriod time.Duration) ManagedReconciler {
func NewEventingAuthReconciler(c client.Client, s *runtime.Scheme, errorRequeuePeriod time.Duration, defaultRequeuePeriod time.Duration) ManagedReconciler {
return &eventingAuthReconciler{
Client: c,
Scheme: s,
IasClient: ias,
errorRequeuePeriod: errorRequeuePeriod,
defaultRequeuePeriod: defaultRequeuePeriod,
}
Expand All @@ -79,6 +84,13 @@ func (r *eventingAuthReconciler) Reconcile(ctx context.Context, req ctrl.Request
}, err
}

// sync IAS client credentials
r.iasClient, err = r.getIasClient()
if err != nil {
return ctrl.Result{
RequeueAfter: r.errorRequeuePeriod,
}, err
}
// check DeletionTimestamp to determine if object is under deletion
if cr.ObjectMeta.DeletionTimestamp.IsZero() {
if err = r.addFinalizer(ctx, &cr); err != nil {
Expand All @@ -88,7 +100,7 @@ func (r *eventingAuthReconciler) Reconcile(ctx context.Context, req ctrl.Request
}
} else {
logger.Info("Handling deletion", "eventingAuth", cr.Name, "eventingAuthNamespace", cr.Namespace)
if err = r.handleDeletion(ctx, targetK8sClient, &cr); err != nil {
if err = r.handleDeletion(ctx, r.iasClient, targetK8sClient, &cr); err != nil {
return ctrl.Result{
RequeueAfter: r.errorRequeuePeriod,
}, err
Expand All @@ -107,7 +119,7 @@ func (r *eventingAuthReconciler) Reconcile(ctx context.Context, req ctrl.Request

if !appSecretExists {
logger.Info("Creating IAS application", "eventingAuth", cr.Name, "eventingAuthNamespace", cr.Namespace)
iasApplication, createAppErr := r.IasClient.CreateApplication(ctx, cr.Name)
iasApplication, createAppErr := r.iasClient.CreateApplication(ctx, cr.Name)
if createAppErr != nil {
logger.Error(createAppErr, "Failed to create IAS application", "eventingAuth", cr.Name, "eventingAuthNamespace", cr.Namespace)
if err := r.updateEventingAuthStatus(ctx, &cr, operatorv1alpha1.ConditionApplicationReady, createAppErr); err != nil {
Expand Down Expand Up @@ -165,6 +177,36 @@ func (r *eventingAuthReconciler) Reconcile(ctx context.Context, req ctrl.Request
}, nil
}

func (r *eventingAuthReconciler) getIasClient() (ias.Client, error) {
namespace, name := getIasSecretNamespaceAndNameConfigs()
newIasCredentials, err := ias.ReadCredentials(namespace, name, r.Client)
if err != nil {
return nil, err
}
// return from cache unless credentials are changed
if r.iasClient != nil && reflect.DeepEqual(r.iasClient.GetCredentials(), newIasCredentials) {
return r.iasClient, nil
}
// update IAS client if credentials are changed
iasClient, err := ias.NewIasClient(newIasCredentials.URL, newIasCredentials.Username, newIasCredentials.Password)
if err != nil {
return nil, fmt.Errorf("failed to createa a new IAS client: %v", err)
}
return iasClient, nil
}

func getIasSecretNamespaceAndNameConfigs() (string, string) {
namespace := os.Getenv(IasCredsSecretNamespace)
if len(namespace) == 0 {
namespace = defaultIasCredsNamespaceName
}
name := os.Getenv(IasCredsSecretName)
if len(name) == 0 {
name = defaultIasCredsSecretName
}
return namespace, name
}

// Adds the finalizer if none exists
func (r *eventingAuthReconciler) addFinalizer(ctx context.Context, cr *operatorv1alpha1.EventingAuth) error {
if !controllerutil.ContainsFinalizer(cr, eventingAuthFinalizerName) {
Expand All @@ -178,7 +220,7 @@ func (r *eventingAuthReconciler) addFinalizer(ctx context.Context, cr *operatorv
}

// Deletes the secret and IAS app. Finally, removes the finalizer.
func (r *eventingAuthReconciler) handleDeletion(ctx context.Context, targetClusterClient client.Client, cr *operatorv1alpha1.EventingAuth) error {
func (r *eventingAuthReconciler) handleDeletion(ctx context.Context, iasClient ias.Client, targetClusterClient client.Client, cr *operatorv1alpha1.EventingAuth) error {
// The object is being deleted
if controllerutil.ContainsFinalizer(cr, eventingAuthFinalizerName) {
// delete k8s secret
Expand All @@ -188,7 +230,7 @@ func (r *eventingAuthReconciler) handleDeletion(ctx context.Context, targetClust
ctrl.Log.Info("Deleted IAS application secret on target cluster", "eventingAuth", cr.Name, "namespace", cr.Namespace)

// delete IAS application clean-up
if err := r.IasClient.DeleteApplication(ctx, cr.Name); err != nil {
if err := iasClient.DeleteApplication(ctx, cr.Name); err != nil {
return fmt.Errorf("failed to delete IAS Application: %v", err)
}
ctrl.Log.Info("Deleted IAS Application", "name", cr.Name)
Expand Down Expand Up @@ -256,7 +298,7 @@ func hasTargetClusterApplicationSecret(ctx context.Context, c client.Client) (bo
Namespace: applicationSecretNamespace,
}, &s)

if errors.IsNotFound(err) {
if apiErrors.IsNotFound(err) {
return false, nil
}

Expand Down
70 changes: 48 additions & 22 deletions controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,31 @@ import (

operatorv1alpha1 "github.com/kyma-project/eventing-auth-manager/api/v1alpha1"
//+kubebuilder:scaffold:imports
"log"
)

// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.

const (
defaultTimeout = time.Second * 5
defaultTimeout = time.Second * 10
namespace = "test-namespace"
)

var (
cfg *rest.Config
k8sClient client.Client
ctx context.Context
cancel context.CancelFunc
targetClusterK8sCfg string
targetClusterK8sClient client.Client
testEnv *envtest.Environment
targetClusterTestEnv *envtest.Environment
cfg *rest.Config
k8sClient client.Client
ctx context.Context
cancel context.CancelFunc
targetClusterK8sCfg string
targetClusterK8sClient client.Client
testEnv *envtest.Environment
targetClusterTestEnv *envtest.Environment
originalNewIasClientFunc func(iasTenantUrl, user, password string) (ias.Client, error)
originalReadCredentialsFunc func(namespace, name string, k8sClient client.Client) (*ias.Credentials, error)
iasUrl string
iasUsername string
iasPassword string
)

func TestAPIs(t *testing.T) {
Expand All @@ -76,6 +82,10 @@ var _ = BeforeSuite(func(specCtx SpecContext) {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
ctx, cancel = context.WithCancel(context.TODO())

iasUrl = os.Getenv("TEST_EVENTING_AUTH_IAS_URL")
iasUsername = os.Getenv("TEST_EVENTING_AUTH_IAS_USER")
iasPassword = os.Getenv("TEST_EVENTING_AUTH_IAS_PASSWORD")

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
Expand Down Expand Up @@ -126,7 +136,23 @@ var _ = BeforeSuite(func(specCtx SpecContext) {
})
Expect(err).NotTo(HaveOccurred())

reconciler := controllers.NewEventingAuthReconciler(mgr.GetClient(), mgr.GetScheme(), getIasTestClient(), time.Second*1, time.Second*3)
// mock ias.ReadCredentials function
originalReadCredentialsFunc = ias.ReadCredentials
ias.ReadCredentials = func(namespace, name string, k8sClient client.Client) (*ias.Credentials, error) {
return &ias.Credentials{URL: iasUrl, Username: iasUsername, Password: iasPassword}, nil
}

if !existIasCreds() {
// use IasClient stub unless test IAS ENV vars exist
log.Println("Using mock IAS client as TEST_EVENTING_AUTH_IAS_URL, TEST_EVENTING_AUTH_IAS_USER, and TEST_EVENTING_AUTH_IAS_PASSWORD are missing")
originalNewIasClientFunc = ias.NewIasClient
mockNewIasClientFunc := func(iasTenantUrl, user, password string) (ias.Client, error) {
return iasClientStub{}, nil
}
ias.NewIasClient = mockNewIasClientFunc
}

reconciler := controllers.NewEventingAuthReconciler(mgr.GetClient(), mgr.GetScheme(), time.Second*1, time.Second*3)

Expect(reconciler.SetupWithManager(mgr)).Should(Succeed())

Expand All @@ -139,6 +165,10 @@ var _ = BeforeSuite(func(specCtx SpecContext) {

var _ = AfterSuite(func() {
cancel()
ias.ReadCredentials = originalReadCredentialsFunc
if !existIasCreds() {
ias.NewIasClient = originalNewIasClientFunc
}
By("Tearing down the test environment")
stopTestEnv(testEnv)
stopTestEnv(targetClusterTestEnv)
Expand All @@ -154,18 +184,8 @@ func stopTestEnv(env *envtest.Environment) {
Expect(err).NotTo(HaveOccurred())
}

func getIasTestClient() ias.Client {
url := os.Getenv("TEST_EVENTING_AUTH_IAS_URL")
user := os.Getenv("TEST_EVENTING_AUTH_IAS_USER")
pw := os.Getenv("TEST_EVENTING_AUTH_IAS_PASSWORD")

if url != "" && user != "" && pw != "" {
iasClient, err := ias.NewIasClient(url, user, pw)
Expect(err).NotTo(HaveOccurred())
return iasClient
} else {
return iasClientStub{}
}
func existIasCreds() bool {
return iasUrl != "" && iasUsername != "" && iasPassword != ""
}

type iasClientStub struct {
Expand All @@ -179,13 +199,18 @@ func (i iasClientStub) DeleteApplication(_ context.Context, _ string) error {
return nil
}

func (i iasClientStub) GetCredentials() *ias.Credentials {
return &ias.Credentials{}
}

func initTargetClusterConfig() (client.Client, error) {
targetK8sCfgPath := os.Getenv("TEST_EVENTING_AUTH_TARGET_KUBECONFIG_PATH")

var err error
var clientConfig *rest.Config

if targetK8sCfgPath == "" {
log.Println("Starting local KubeAPI and ETCD server as target TEST_EVENTING_AUTH_TARGET_KUBECONFIG_PATH is missing")
targetClusterTestEnv = &envtest.Environment{}

clientConfig, err = targetClusterTestEnv.Start()
Expand All @@ -203,6 +228,7 @@ func initTargetClusterConfig() (client.Client, error) {
Expect(err).NotTo(HaveOccurred())
targetClusterK8sCfg = base64.StdEncoding.EncodeToString(kubeconfig)
} else {
log.Println("Using a K8s cluster with kubeconfig file: " + targetK8sCfgPath)
kubeconfig, err := os.ReadFile(targetK8sCfgPath)
Expect(err).NotTo(HaveOccurred())
targetClusterK8sCfg = base64.StdEncoding.EncodeToString(kubeconfig)
Expand Down
18 changes: 14 additions & 4 deletions internal/ias/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import (
type Client interface {
CreateApplication(ctx context.Context, name string) (Application, error)
DeleteApplication(ctx context.Context, name string) error
GetCredentials() *Credentials
}

func NewIasClient(iasTenantUrl, user, password string) (Client, error) {
var NewIasClient = func(iasTenantUrl, user, password string) (Client, error) {

basicAuthProvider, err := securityprovider.NewSecurityProviderBasicAuth(user, password)
if err != nil {
Expand All @@ -37,8 +38,9 @@ func NewIasClient(iasTenantUrl, user, password string) (Client, error) {
}

return &client{
api: apiClient,
oidcClient: oidc.NewOidcClient(oidcHttpClient, iasTenantUrl),
api: apiClient,
oidcClient: oidc.NewOidcClient(oidcHttpClient, iasTenantUrl),
credentials: &Credentials{URL: iasTenantUrl, Username: user, Password: password},
}, nil
}

Expand All @@ -47,7 +49,15 @@ type client struct {
oidcClient oidc.Client
// The token URL of the IAS client. Since this URL should only change when the tenant changes and this will lead to the initialization of
// a new client, we can cache the URL to avoid an additional request at each application creation.
tokenUrl *string
tokenUrl *string
credentials *Credentials
}

func (c *client) GetCredentials() *Credentials {
if c.credentials == nil {
c.credentials = &Credentials{}
}
return c.credentials
}

// CreateApplication creates an application in IAS. This function is not idempotent, because if an application with the specified
Expand Down
Loading

0 comments on commit 878c11f

Please sign in to comment.