Skip to content

Commit

Permalink
feat: create project level authorino authconfig
Browse files Browse the repository at this point in the history
* Setup namespace annotations for gatewway/host information used by this namesapce
* Generate host pattern based on openshift ingress configs AppDomain
* Generate a default AuthConfig file using ServiceAccounts TokenReview process as auth
* Rely on endpoint in `osh-model-controller` for anonymous access support

releated-to: #58
  • Loading branch information
aslakknutsen committed Oct 24, 2023
1 parent 3e04a6c commit 0247d2c
Show file tree
Hide file tree
Showing 14 changed files with 523 additions and 89 deletions.
2 changes: 1 addition & 1 deletion config/crd/external/authorino.kuadrant.io_authconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.12.0
controller-gen.kubebuilder.io/version: v0.12.1
name: authconfigs.authorino.kuadrant.io
spec:
group: authorino.kuadrant.io
Expand Down
334 changes: 334 additions & 0 deletions config/crd/external/ingress.config.openshift.io.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion config/crd/external/route.openshift.io_routes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.12.0
controller-gen.kubebuilder.io/version: v0.12.1
name: routes.route.openshift.io
spec:
group: route.openshift.io
Expand Down
8 changes: 8 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ rules:
- patch
- update
- watch
- apiGroups:
- config.openshift.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- maistra.io
resources:
Expand Down
15 changes: 4 additions & 11 deletions controllers/enable_authorino.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ type reconcileFunc func(ctx context.Context, namespace *v1.Namespace) error
func (r *OpenshiftServiceMeshReconciler) reconcileAuthConfig(ctx context.Context, namespace *v1.Namespace) error {
log := r.Log.WithValues("feature", "authorino", "namespace", namespace.Name)

desiredAuthConfig, err := r.createAuthConfig(namespace, namespace.ObjectMeta.Annotations[AnnotationPublicGatewayExternalHost])
desiredAuthConfig, err := r.createAuthConfig(namespace,
namespace.ObjectMeta.Annotations[AnnotationProjectModelGatewayHostPatternExternal],
namespace.ObjectMeta.Annotations[AnnotationProjectModelGatewayHostPatternInternal])
if err != nil {
log.Error(err, "Failed creating AuthConfig object")

Expand Down Expand Up @@ -103,16 +105,7 @@ func (r *OpenshiftServiceMeshReconciler) createAuthConfig(namespace *v1.Namespac

authConfig.Labels[keyValue[0]] = keyValue[1]
authConfig.Spec.Hosts = authHosts

// Reflects oauth-proxy SAR settings
authConfig.Spec.Authorization[0].KubernetesAuthz.ResourceAttributes = &authorino.Authorization_KubernetesAuthz_ResourceAttributes{ //nolint:nosnakecase //reason external library
Namespace: authorino.StaticOrDynamicValue{Value: namespace.Name},
Group: authorino.StaticOrDynamicValue{Value: "kubeflow.org"},
Resource: authorino.StaticOrDynamicValue{Value: "notebooks"},
Verb: authorino.StaticOrDynamicValue{Value: "get"},
}

authConfig.Spec.Identity[0].KubernetesAuth.Audiences = getAuthAudience()
authConfig.Spec.Identity[0].KubernetesAuth.Audiences = []string{namespace.Name + "-api"}

return authConfig, nil
}
Expand Down
18 changes: 16 additions & 2 deletions controllers/enable_mesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"reflect"
"strconv"

configv1 "github.com/openshift/api/config/v1"
routev1 "github.com/openshift/api/route/v1"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -120,8 +121,6 @@ func (r *OpenshiftServiceMeshReconciler) findIstioIngress(ctx context.Context) (
LabelSelector: labels.SelectorFromSet(labels.Set{"app": "odh-dashboard"}),
Namespace: meshNamespace,
}); err != nil {
r.Log.Error(err, "Unable to find matching gateway")

return routev1.RouteList{}, errors.Wrap(err, "unable to find matching gateway")
}

Expand All @@ -136,3 +135,18 @@ func (r *OpenshiftServiceMeshReconciler) findIstioIngress(ctx context.Context) (

return routes, nil
}

func (r *OpenshiftServiceMeshReconciler) findAppDomain(ctx context.Context) (string, error) {
ingress := configv1.Ingress{}

err := r.Client.Get(ctx, types.NamespacedName{Name: "cluster"}, &ingress)
if err != nil {
return "", errors.Wrap(err, "unable to find matching ingress config cluster")
}

if ingress.Spec.AppsDomain != "" {
return ingress.Spec.AppsDomain, nil
}

return ingress.Spec.Domain, nil
}
2 changes: 2 additions & 0 deletions controllers/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controllers

import (
authorino "github.com/kuadrant/authorino/api/v1beta1"
configv1 "github.com/openshift/api/config/v1"
routev1 "github.com/openshift/api/route/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand All @@ -14,5 +15,6 @@ func RegisterSchemes(s *runtime.Scheme) {
utilruntime.Must(clientgoscheme.AddToScheme(s))
utilruntime.Must(maistrav1.AddToScheme(s))
utilruntime.Must(routev1.Install(s))
utilruntime.Must(configv1.Install(s))
utilruntime.Must(authorino.AddToScheme(s))
}
11 changes: 0 additions & 11 deletions controllers/mesh_env_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,6 @@ func getAuthorinoLabel() ([]string, error) {
return keyValue, nil
}

func getAuthAudience() []string {
aud := getEnvOr(AuthAudience, "https://kubernetes.default.svc")
audiences := strings.Split(aud, ",")

for i := range audiences {
audiences[i] = strings.TrimSpace(audiences[i])
}

return audiences
}

func getEnvOr(key, defaultValue string) string {
if env, defined := os.LookupEnv(key); defined {
return env
Expand Down
14 changes: 8 additions & 6 deletions controllers/metadata_const.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package controllers

const (
AnnotationServiceMesh = "opendatahub.io/service-mesh"
AnnotationPublicGatewayName = "service-mesh.opendatahub.io/public-gateway-name"
AnnotationPublicGatewayExternalHost = "service-mesh.opendatahub.io/public-gateway-host-external"
AnnotationPublicGatewayInternalHost = "service-mesh.opendatahub.io/public-gateway-host-internal"
LabelMaistraGatewayName = "maistra.io/gateway-name"
LabelMaistraGatewayNamespace = "maistra.io/gateway-namespace"
AnnotationServiceMesh = "opendatahub.io/service-mesh"
AnnotationPublicGatewayName = "service-mesh.opendatahub.io/public-gateway-name"
AnnotationPublicGatewayExternalHost = "service-mesh.opendatahub.io/public-gateway-host-external"
AnnotationPublicGatewayInternalHost = "service-mesh.opendatahub.io/public-gateway-host-internal"
AnnotationProjectModelGatewayHostPatternExternal = "service-mesh.opendatahub.io/model-gateway-hostpattern-external"
AnnotationProjectModelGatewayHostPatternInternal = "service-mesh.opendatahub.io/model-gateway-hostpattern-internal"
LabelMaistraGatewayName = "maistra.io/gateway-name"
LabelMaistraGatewayNamespace = "maistra.io/gateway-namespace"
)
14 changes: 13 additions & 1 deletion controllers/namespace_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
func (r *OpenshiftServiceMeshReconciler) addGatewayAnnotations(ctx context.Context, namespace *v1.Namespace) error {
if namespace.ObjectMeta.Annotations[AnnotationPublicGatewayExternalHost] != "" &&
namespace.ObjectMeta.Annotations[AnnotationPublicGatewayInternalHost] != "" &&
namespace.ObjectMeta.Annotations[AnnotationPublicGatewayName] != "" {
namespace.ObjectMeta.Annotations[AnnotationPublicGatewayName] != "" &&
namespace.ObjectMeta.Annotations[AnnotationProjectModelGatewayHostPatternExternal] != "" &&
namespace.ObjectMeta.Annotations[AnnotationProjectModelGatewayHostPatternInternal] != "" {
// If annotation is present we have nothing to do
return nil
}
Expand All @@ -24,9 +26,19 @@ func (r *OpenshiftServiceMeshReconciler) addGatewayAnnotations(ctx context.Conte
return err
}

appDomain, err := r.findAppDomain(ctx)
if err != nil {
r.Log.Error(err, "unable to find app domain")

return err
}

namespace.ObjectMeta.Annotations[AnnotationPublicGatewayExternalHost] = ExtractHostName(routes.Items[0].Spec.Host)
namespace.ObjectMeta.Annotations[AnnotationPublicGatewayInternalHost] = fmt.Sprintf("%s.%s.svc.cluster.local", routes.Items[0].Spec.To.Name, getMeshNamespace())

namespace.ObjectMeta.Annotations[AnnotationProjectModelGatewayHostPatternExternal] = fmt.Sprintf("*.%s.%s", namespace.Name, appDomain)
namespace.ObjectMeta.Annotations[AnnotationProjectModelGatewayHostPatternInternal] = fmt.Sprintf("*.%s.svc.cluster.local", namespace.Name)

gateway := extractGateway(routes.Items[0].ObjectMeta)
if gateway != "" {
namespace.ObjectMeta.Annotations[AnnotationPublicGatewayName] = gateway
Expand Down
1 change: 1 addition & 0 deletions controllers/project_mesh_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type OpenshiftServiceMeshReconciler struct {
// +kubebuilder:rbac:groups=maistra.io,resources=servicemeshcontrolplanes,verbs=get;list;watch;create;update;patch;use
// +kubebuilder:rbac:groups=maistra.io,resources=servicemeshcontrolplanes,verbs=get;list;watch;create;update;patch;use
// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch
// +kubebuilder:rbac:groups=config.openshift.io,resources=ingresses,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch

// Reconcile ensures that the namespace has all required resources needed to be part of the Service Mesh of Open Data Hub.
Expand Down
18 changes: 13 additions & 5 deletions controllers/project_mesh_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/opendatahub-io/odh-project-controller/test"
. "github.com/opendatahub-io/odh-project-controller/test/cluster"
"github.com/opendatahub-io/odh-project-controller/test/labels"
configv1 "github.com/openshift/api/config/v1"
openshiftv1 "github.com/openshift/api/route/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -33,6 +34,7 @@ var _ = When("Namespace is created", Label(labels.EnvTest), func() {
testNs *corev1.Namespace
objectCleaner *Cleaner
route *openshiftv1.Route
ingressConfig *configv1.Ingress
)

BeforeEach(func() {
Expand Down Expand Up @@ -62,13 +64,23 @@ var _ = When("Namespace is created", Label(labels.EnvTest), func() {
},
},
}
ingressConfig = &configv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
Spec: configv1.IngressSpec{
Domain: "test.io",
AppsDomain: "apps.test.io",
},
}

Expect(cli.Create(context.Background(), istioNs)).To(Succeed())
Expect(cli.Create(context.Background(), route)).To(Succeed())
Expect(cli.Create(context.Background(), ingressConfig)).To(Succeed())
})

AfterEach(func() {
objectCleaner.DeleteAll(istioNs, route, testNs)
objectCleaner.DeleteAll(istioNs, ingressConfig, route, testNs)
})

Context("enabling service mesh", func() {
Expand Down Expand Up @@ -260,8 +272,6 @@ var _ = When("Namespace is created", Label(labels.EnvTest), func() {
// when
_ = os.Setenv(controllers.AuthorinoLabelSelector, "app=rhods")
defer os.Unsetenv(controllers.AuthorinoLabelSelector)
_ = os.Setenv(controllers.AuthAudience, "opendatahub.io,foo , bar")
defer os.Unsetenv(controllers.AuthAudience)

Expect(cli.Create(context.Background(), testNs)).To(Succeed())

Expand All @@ -285,8 +295,6 @@ var _ = When("Namespace is created", Label(labels.EnvTest), func() {
Expect(actualAuthConfig.Name).To(Equal(testNs.GetName() + "-protection"))
Expect(actualAuthConfig.Labels).To(HaveKeyWithValue("app", "rhods"))
Expect(actualAuthConfig.Spec.Hosts).To(Equal(expectedAuthConfig.Spec.Hosts))
Expect(actualAuthConfig.Spec.Identity[0].KubernetesAuth.Audiences).
To(And(HaveLen(3), ContainElements("opendatahub.io", "foo", "bar")))
})
})

Expand Down
79 changes: 60 additions & 19 deletions controllers/template/authconfig.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,74 @@
apiVersion: authorino.kuadrant.io/v1beta1
kind: AuthConfig
metadata:
name: odh-dashboard-protection
name: $ODH_NS-protection
namespace: $ODH_NS
labels:
authorino/topic: odh
spec:
hosts:
- "${ODH_ROUTE}"
- "${ODH_ROUTE}"
identity:
- name: kubernetes-users
kubernetes:
audiences:
- "https://kubernetes.default.svc"
- name: authorized-service-accounts
kubernetes:
audiences:
- $ODH_NS-api
- name: anonymous-grpc
priority: 1
anonymous: {}
when:
- selector: context.request.http.headers.mm-vmodel-id
operator: matches
value: .+
extendedProperties:
- name: modelid
valueFrom:
authJSON: context.request.http.headers.mm-vmodel-id
- name: anonymous-http
priority: 1
anonymous: {}
when:
- selector: context.request.http.headers.mm-vmodel-id
operator: matches
value: ^$
extendedProperties:
- name: modelid
valueFrom:
authJSON: context.request.http.path.@extract:{"sep":"/","pos":2}
metadata:
- name: access
http:
endpoint: http://odh-model-controller-metrics-service.opendatahub.svc.cluster.local:8080/model/?ns={context.request.http.host.@extract:{"sep":".","pos":0}}&modelid={auth.identity.modelid}
#cache:
# key:
# valueFrom:
# authJSON: {context.request.http.host.@extract:{"sep":".","pos":0}}-{auth.identity.modelid}
# ttl: 300
when:
- selector: auth.identity.anonymous
operator: eq
value: "true"
authorization:
- name: k8s-rbac
kubernetes:
user:
valueFrom: { authJSON: auth.identity.username }
response:
- name: x-auth-data
- name: anonymous
when:
- selector: auth.identity.anonymous
operator: eq
value: "true"
json:
properties:
- name: username
valueFrom: { authJSON: auth.identity.username }
rules:
- selector: auth.metadata.access.anonymous
operator: eq
value: "true"
denyWith:
unauthenticated:
message:
value: "Access denied"
code: 302
headers:
- name: Location
valueFrom:
authJSON: http://some-service?redirect_to={context.request.http.path}
unauthorized:
message:
value: "Unauthorized"
code: 403
headers:
- name: Location
valueFrom:
authJSON: http://some-service?redirect_to={context.request.http.path}
Loading

0 comments on commit 0247d2c

Please sign in to comment.