Version: v26.03

Authentication and Authorization Development Guide

Feature Introduction

openFuyao platform provides unified user login authentication service, supporting standard OAuth2.0 login flow. Current version uses internal account password identity provider to perform user authentication and authorization. Future versions will introduce third-party authentication providers to achieve user automatic access. After logging in to openFuyao platform, user access to specific resources of Kubernetes APIServer and access to components within openFuyao system depend on Kubernetes RBAC authorization mechanism.

In addition, openFuyao can conveniently integrate developers' own developed frontend and backend applications through general easy-to-extend architecture. If developers want to integrate their own developed applications into openFuyao's entire authentication and authorization system, they can use oauth-proxy component to implement it. As a sidecar container, it will first intercept requests sent to user's frontend application, and only allow access to user's frontend application after successfully performing OAuth2.0 (Open Authorization 2.0) authentication and RBAC (Role-Based Access Control) authorization, thereby completing authentication and authorization of extension components.

  • User basic login authentication

    • Initial password is generated after one-click installation and deployment.
    • First login requires password modification.
    • User modifies password, user logs out.
  • User authorization

    • Access to Kubernetes resources is authorized by delegating to Kubernetes APIServer for RBAC authorization.
    • Access to component permissions is authorized by oauth-proxy proxy to Kubernetes APIServer for RBAC authorization.
  • Security verification

    • User is locked when multiple login failures occur to prevent brute force attacks.
    • User authorization step in OAuth2.0 authentication uses csrf_token verification to prevent cross-site request forgery attacks.
  • Extension components

    • Can access openFuyao's OAuth2.0 authentication flow.
    • Can access Kubernetes RBAC authorization based on abstracted user Role using this component.

Constraints and Limitations

Current version does not support other identity providers (github) intervening in OAuth2.0 authentication process, can only use built-in fuyaoPasswordProvider identity provider.

Environment Preparation

Authentication and authorization system is installed and deployed together with platform. For detailed steps, please refer to Installation Guide.

Integrating Extension Components into openFuyao Authentication System

Task Scenario Overview

When users develop an extension component's frontend and backend themselves, they will integrate it into openFuyao management interface through openFuyao's extension framework. If users want users accessing this extension component to have specific permissions at this time, they need to introduce authentication and authorization component oauth-proxy to help extension frontend access openFuyao's authentication and authorization system, thereby filtering out users without permissions and protecting user's upstream application.

System Architecture

From deployment perspective, in order to integrate extension component's frontend into authentication and authorization system, we will add oauth-proxy sidecar container to extension component's frontend pod. It will intercept requests sent to extension component frontend and perform authentication verification. After authentication passes, it will also delegate to KubeAPIServer for authorization through SubjectAccessReview, and only then forward requests to extension component frontend inside pod.

Figure 1 Extension Component Integration into openFuyao Authentication and Authorization System Flow Diagram

The entire integration process shown in Figure 1 can be decomposed into following four steps.

  1. Line 1 represents browser accessing openfuyao management interface's frontend display interface basic information, javascript on interface will request ConsolePlugin CRD mounted in current cluster.
  2. Line 2 represents processing process of requesting ConsolePlugin CRD, Console-Service will forward to APIServer to get all CRs and return to browser.
  3. Line 3 represents browser obtaining mount information and extension prefix, forwarding requests to oauth-proxy sidecar in extension component frontend pod through Console-Service's extension routing. oauth-proxy will parse user token to obtain user information, then delegate to kube-apiserver for authorization before being able to access extension component frontend page, return to browser.
  4. Line 4 represents frontend obtaining browser extension component's backend through javascript, similarly forwarding to oauth-proxy sidecar through Console-Service's extension routing. After authentication and authorization pass, forward to extension component frontend service, which internally forwards to backend service through configured nginx.conf.

Development Process

Only need to modify configuration file, no detailed development process required.

Interface Description

Only need to modify configuration file, no need to introduce interfaces.

Development Steps

Here taking log component as example to explain complete process of integrating extension component into openFuyao authentication system.

NoteNote:
Following configurations can be performed through values.yaml in oauth-proxy's helm template. Configuration items involved are shown as follows.

yaml
# values.yaml
......
service:
  containerPort: 9039  # Business container's containerPort, i.e., port opened by container internal http server 
  backend: "http://logging-operator.openfuyao-system.svc" # Extension component backend url 
  enableOAuth: true   # Whether to enable oauth-proxy
oauthProxy:
  containerPort: 9093
  image:
    repository: "cr.openfuyao.cn/openfuyao/oauth-proxy"
    pullPolicy: Always
    tag: "v24.09"
  client:
    id: "oauth-proxy"
    secret: "SECRETTS"
  urls:
    host: "/"
    loginURI: "/oauth2/oauth/authorize"
    redeemURI: "/oauth2/oauth/token"
    rootPrefix: "/logging" # Fill in extension component consolePlugin CR's .spec.pluginName
  1. Original deployment file preparation.

    Extension component is installed by Helm. Normally there will be two Helm packages corresponding to extension component's frontend and backend respectively. And extension component's backend services belong to cluster. All external calls will first pass through extension component's frontend proxy and be sent to extension component's backend. Therefore, our authentication only needs to be added to extension component's frontend to filter all requests accessing extension component on page. Here taking log extension component as example to explain files that need to be prepared.

    yaml
    # Source: logging/templates/consoleplugin.yaml
    apiVersion: console.openfuyao.com/v1beta1
    kind: ConsolePlugin
    metadata:
      name: logging
    spec:
      pluginName: logging      # Name of this extension component, needs to be set, will be used when configuring Ingress later
      displayName: "Logs"
      subPages:
        - pageName: logSearch
          displayName: "Log Query"
        - pageName: logSet
          displayName: "Log Configuration"
      entrypoint: /container_platform
      backend:
        type: Service
        service:
          name: logging  # {{ .Release.Name }}
          namespace: openfuyao-system   # {{ .Release.Namespace }}
          port: 80  # {{ .Values.service.httpPort }}
      enabled: true
    ---
    # Source: logging/templates/fe-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: logging
      namespace: openfuyao-system
      labels:
        app: logging
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: logging
      template:
        metadata:
          labels:
            app: logging
        spec:
          containers:
            - name: frontend
              image: "cr.openfuyao.cn/openfuyao/logging-website:v24.09"
              imagePullPolicy: Always
              ports:
                - name: http
                  containerPort: 80
                  protocol: TCP
              volumeMounts:
                - name: host-time
                  mountPath: /etc/localtime
                  readOnly: true
                - name: nginx-config
                  mountPath: /etc/nginx/nginx.conf
                  subPath: nginx.conf
          volumes:
            - name: host-time
              hostPath:
                path: /etc/localtime
                type: ""
            - name: nginx-config
              configMap:
                defaultMode: 0600
                name: logging-nginx
                items:
                - key: nginx.conf
                  path: nginx.conf
    ---
    # Source: logging/templates/fe-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: logging
      namespace: openfuyao-system
      labels:
        app: logging
    spec:
      type: ClusterIP
      ports:
        - port: 80 # {{ .Values.service.httpPort }}
          targetPort: 8080 # {{ .Values.service.containerPort }}
      selector:
        app: logging
    ---
    # Source: logging/templates/fe-config.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: logging-nginx
      namespace: openfuyao-system
    data:
      nginx.conf: |
        worker_processes  auto;
    
        error_log  /var/log/nginx/error.log notice;
        pid        /tmp/nginx.pid;
    
        events {
            worker_connections  1024;
        }
    
        http {
            proxy_temp_path /tmp/proxy_temp;
            client_body_temp_path /tmp/client_temp;
            fastcgi_temp_path /tmp/fastcgi_temp;
            uwsgi_temp_path /tmp/uwsgi_temp;
            scgi_temp_path /tmp/scgi_temp;
    
            include       /etc/nginx/mime.types;
            default_type  application/octet-stream;
    
            log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                              '$status $body_bytes_sent "$http_referer" '
                              '"$http_user_agent" "$http_x_forwarded_for"';
    
            access_log  /var/log/nginx/access.log  main;
    
            sendfile        on;
            #tcp_nopush     on;
    
            gzip on;
            gzip_min_length 10k;
            gzip_buffers 4 32k;
            gzip_http_version 1.1;
            gzip_comp_level 6;
            gzip_types text/plain text/css text/html text/xml text/javascript application/json application/x-javascript application/xml application/xml+rss application/javascript font/ttf font/woff font/woff2 image/png image/svg+xml video/mp4;
            gzip_vary on;
    
            client_body_timeout 10;
            client_header_timeout 10;
            keepalive_timeout 5 30;
            send_timeout 10;
    
            port_in_redirect off;
    
            limit_conn_zone $binary_remote_addr zone=limitperip:10m;
            limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=5r/s;
    
            server_tokens off;
            autoindex off;
    
            server {
                listen 8080 default_server;
                root /usr/share/nginx/html/;
                index index.html index.htm;
    
                client_header_buffer_size 5k;
                large_client_header_buffers 4 16k;
                client_body_buffer_size 10m;
                client_max_body_size 256m;
             
                add_header Content-Security-Policy "connect-src 'self' https:;frame-ancestors 'none';object-src 'none'" always;
                add_header Cache-Control "no-cache, no-store, must-revalidate" always;
                add_header X-Frame-Options DENY always;
                add_header X-Content-Type-Options nosniff always;
                add_header X-XSS-Protection 1 always;
                add_header Strict-Transport-Security "max-age=31536000" always;
    
                limit_conn limitperip 10;
    
                location / {
                    try_files $uri /index.html;
                }
    
                location ~* ^/logging/.*\.(js|cjs|mjs)$ {
                    default_type text/javascript;
                    rewrite ^ /dist/logging.mjs break;
                }
    
                location /logging/ {
                    rewrite ^/logging(/.*)$ $1 break;
                    proxy_pass {{ .Values.backend }};
                }
    
                location ~* \.(js|cjs|mjs)$ {
                    default_type text/javascript;
                }
             
                location /rest/logging {
                    proxy_pass {{ .Values.backend }};
                }
            }
        }

    The above four yaml files are needed for deploying extension component frontend. Among them, .spec.pluginName in custom resource ConsolePlugin needs to be set to name of this extension component, will be used when Console-Service performs forwarding later. fe-service.yaml and fe-deployment.yaml are extension component frontend's service and deployment resources respectively, oauth-proxy resources will be added later to complete authentication and authorization integration. fe-config.yaml contains extension component frontend nginx configuration items, responsible for forwarding requests to extension component backend.

  2. Configure Service.

    yaml
    # Source: proxy-logging/templates/fe-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: logging
      namespace: openfuyao-system
      labels:
        app: logging
    spec:
      type: ClusterIP
      ports:
        - port: 80 # {{ .Values.service.httpPort }}
          targetPort: proxy      # Modified from http to proxy
      selector:
        app: logging

    When configuring Service, change original port 8080 pointing to extension component frontend container to port name proxy pointing to oauth-proxy container. Proxy corresponding port number can be configured through {{ .Values.oauthProxy.containerPort }}. After this, external access to this extension component Pod will first be intercepted by oauth-proxy sidecar, and then after authentication passes, sent to original extension component container's port through Pod internal localhost communication.

  3. Configure ServiceAccount and related Role/RoleBinding.

    yaml
    # Source: proxy-logging/templates/serviceaccount-rolebindings.yaml
    # RBAC authn and authz
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: logging-oauth-proxy
      namespace: openfuyao-system
    ---
    # Source: proxy-logging/templates/serviceaccount-rolebindings.yaml
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: logging-oauth-proxy-webhook-auth
    subjects:
      - kind: ServiceAccount
        name: logging-oauth-proxy
        namespace: openfuyao-system
    roleRef:
      kind: ClusterRole
      name: webhook-auth
      apiGroup: rbac.authorization.k8s.io

    In above configuration files:

    • Files corresponding to serviceaccount-rolebindings.yaml in helm can be used directly, templates contained are configured in values.yaml according to project requirements.
    • logging-oauth-proxy: Service account used by oauth-proxy, below will bind this service account to some roles granting it certain permissions to enable it to perform authentication API calls.
    • logging-oauth-proxy-webhook-auth: A cluster role binding of oauth-proxy, granting oauth-proxy permissions to call authentication and authorization webhooks in Kubernetes APIServer. Corresponding ClusterRole has already been created when oauth-server was installed, no need to create duplicate here.

    If ServiceAccount is already bound in Deployment corresponding to frontend service, then logging-oauth-proxy this ServiceAccount does not need to be created, in logging-oauth-proxy-webhook-auth this ClusterRoleBinding's subjects corresponding name just fill in already bound ServiceAccount.

  4. Configure Deployment.

    NoteNote:
    After users complete first 3 steps, step 4 only needs to directly copy and paste content between {{- if .Values.enableOAuth }}...{{- end }} to corresponding position.

    Since oauth-proxy is added to original extension component frontend's Pod in form of sidecar, here in fe-deployment.yaml we will additionally add an oauth-proxy container deployment, specific form is as follows.

    yaml
    # Source: proxy-logging/templates/fe-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: logging
      namespace: openfuyao-system
      labels:
        app: logging
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: logging
      template:
        metadata:
          labels:
            app: logging
        spec:
          serviceAccountName: logging-oauth-proxy # If original Deployment file does not have this item, use ServiceAccount created in step 2
          containers:
            - name: logging
              image: "cr.openfuyao.cn/openfuyao/logging-website:v24.09"  # Extension component frontend container
              imagePullPolicy: Always
              ports:
                - name: http
                  containerPort: 80
                  protocol: TCP
            # Add oauth-proxy image
            {{- if .Values.enableOAuth }}
            - name: oauth-proxy
              image: "{{ .Values.oauthProxy.image.repository }}:{{ .Values.oauthProxy.image.tag }}"
              imagePullPolicy: {{ .Values.oauthProxy.image.pullPolicy }}
              ports:
                - containerPort: {{ .Values.oauthProxy.containerPort }}
                  name: proxy
              args:
                - --https-address=
                - '--http-address=:{{ .Values.oauthProxy.containerPort }}'
                - --email-domain=*
                - --provider=openfuyao
                - --client-id={{ .Values.oauthProxy.client.id }}
                - --client-secret={{ .Values.oauthProxy.client.secret }}
                - '--upstream=http://localhost:{{ .Values.service.containerPort }}'
                - '--openfuyao-delegate-urls={"/":{"resource": "services/proxy", "group": ""}}'
                - --redirect-url={{ .Values.oauthProxy.urls.host }}
                - --login-url={{ .Values.oauthProxy.urls.loginURI }}
                - --redeem-url={{ .Values.oauthProxy.urls.redeemURI }}
                - --root-prefix={{ .Values.oauthProxy.urls.rootPrefix }}
                - --cookie-httponly
            {{- end }}
          volumes:
            - name: host-time
              hostPath:
                path: /etc/localtime
                type: ""
            - name: nginx-config
              configMap:
                defaultMode: 0600
                name: logging-nginx
                items:
                - key: nginx.conf
                  path: nginx.conf

Testing and Verification

None.