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

Read ias secret #11

Merged
merged 6 commits into from
Apr 27, 2023
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
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)
muralov marked this conversation as resolved.
Show resolved Hide resolved

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