Skip to content

Security: Microkubes/microkubes-website

Security

docs/Security.md

id title
security
Security

The security of the Microkubes platform is implemented at microservice level. Our integrated user management ensures security of data by using OAuth2, JWT and SAML for user authentication and authorization. It eliminates the risk of password theft or reuse. The platform provides fine-grained access control to each object in Microkubes. An ACL specifies which users or system processes are granted access to objects, as well as what operations are allowed on given objects. Each entry in a typical ACL specifies a subject and an operation. This all comes out of the box with Microkubes platform.

Until now we have security support for Flask(Python) and Goa Design(Golang) approaches for building microservices.

Golang package

This microservice-security contains functions that are commonly used by all microservices for setting up the security. Also exposes functions to set up different security mechanisms for securing the microservices.

The security library offers golang API for setting up security based on your needs. It comes with these security providers out-of-the-box:

  • JWT provider - can decode and validate JWTs generated by Microkubes JWT Issuer service
  • OAuth2 provider - can decode and validate OAuth2 tokens generated by the OAuth2 Authorization Serve in Microkubes.
  • SAML service provider - can decode and validate JWTs tokens generated by the Identity Provider in Microkubes. It adds endpoint to each microservice which parses SAML response from Identity Provider and sends microservice metadata to Identity Provider
  • ACL middleware - provides access control lists based authorization for the microservices.

There are two important packages:

  • auth - defines the standard Auth object and offers functions for manipulating the request context for setting/getting the Auth object.
  • chain - defines the SecurityChain and function for creating new security middleware and registering it with the security chain.

Manipulating the security Auth

Auth object can be manipulated in the request context with helpers of the auth package. The package exposes function for checking, setting and getting the Auth object from the context.

To get the current Auth object from the context, you can use auth.GetAuth(context.Context) helper.

authObj := auth.GetAuth(ctx)

if authObj == nil {
  // The context contains no Auth
}

In the Goa controllers you can use Auth Context to get the user's information:

  if !auth.HasAuth(ctx.Context) {
    return ctx.InternalServerError(goa.ErrBadRequest("no-auth"))
  }

    userID := auth.GetAuth(ctx.Context).UserID
    roles := auth.GetAuth(ctx.Context).Roles

    ...

The auth context stores the Authorization and Authentication data for a particular user/client:

  • UserID - ID of the authenticated user
  • Username - The email of the authenticated user
  • Roles - Roles is the list of roles that the user has claimed and have been authorized by the system.
  • Organizations - Organizations is the list of organizations that the user belongs to. This is a list of authorized ogranization based on the security claim.
  • Namespaces - Namespaces is the list of namespaces that this user belongs to.

Security Chain

SecurityChain is a standard chain of processing of the incoming http request. It is intended to be used as a middleware in Goa infrastructure (although it can execute by its own using the standard http Handlers by Go).

SecurityChain is an interface residing in the chain package. This package also provides other helper functions related to creating the chain, wrapping it in goa.Midlleware or wrapping a Goa middleware in the chain itself.

The chain is composed of a list of middleware functions of type chain.SecurityChainMiddleware. The signature of this function is:

func (context.Context, http.ResponseWriter, *http.Request) (context.Context, http.ResponseWriter, error)

The chain executes the middleware functions in the order they are registered. The input context and ResponseWriter for each middleware is the output of the previous middleware. The resulting context, ResponseWriter and Request are returned back by the SecurityChain.Execute(...) method.

Setting up a security for a microservice

The easier way to set up a security is to use the flow package and the helper flow.NewSecurityFromConfig().

In the microservice main file, you first need to load the microservice configuration. The pass the configuration to the helper and create new SecurityChain.

Finally you need to add the security chain as a middleware to the service itself.

func main(){

  gatewayAdminURL, configFile := loadGatewaySettings()

  // 1. Load the configuration
  serviceConfig, err := config.LoadConfig(configFile)
  if err != nil {
    service.LogError("config", "err", err)
    return
  }

  // 2. Create a security chain
  securityChain, cleanup, err := flow.NewSecurityFromConfig(conf)
  if err != nil {
  // There was a problem setting up the security
    service.LogError("config", "err", err)
    return
  }

  defer cleanup()

  // ... Goa service and controllers setup

  // 3. Finally add the security chain to the service as a middleware.
  service.Use(chain.AsGoaMiddleware(securityChain))
}

Security configuration

The whole configuration for the security chain is read from config file. The service that has set up security from config should have proper configuration for each type.

The package looks for the security property in the configuration. It contains settings for all security types including ACL policies. If some type is ommited then that type of security will not be set. If security property is ommited from config file then service will not have security set.

The structure of security configuration is pretty simple. Example:

...

"security": {
    "keysDir": "/run/secrets",
    "ignorePatterns": [
      "/users/verify"
    ],
    "jwt": {
      "name": "JWTSecurity",
      "description": "JWT security middleware",
      "tokenUrl": "http://kong:8000/jwt"
    },
    "saml": {
      "certFile": "/run/secrets/service.cert",
      "keyFile": "/run/secrets/service.key",
      "identityProviderUrl": "http://kong:8000/saml/idp",
      "userServiceUrl": "http://kong:8000/users",
      "registrationServiceUrl": "http://kong:8000/users/register",
      "rootURL": "http://localhost:8000/users"
    },
    "oauth2": {
      "description": "OAuth2 security middleware",
      "tokenUrl": "https://kong:8000/oauth2/token",
      "authorizeUrl": "https://kong:8000/oauth2/authorize"
    },
    "acl": {
      "policies": [
        {
          "id": "users-allow-admin-access",
          "description": "Allows access to everything to an admin user",
          "resources": [
            "<.+>"
          ],
          "actions": [
            "api:read",
            "api:write"
          ],
          "effect": "allow",
          "subjects": [
            "<.+>"
          ],
          "conditions": {
            "roles": {
              "type": "RolesCondition",
              "options": {
                "values": [
                  "admin",
                  "system"
                ]
              }
            }
          }
        },
        {
          "id": "users-allow-read-access",
          "description": "Allows access to user's own profile",
          "resources": [
            "/users/me"
          ],
          "actions": [
            "api:read"
          ],
          "effect": "allow",
          "subjects": [
            "<.+>"
          ],
          "conditions": {
            "roles": {
              "type": "RolesCondition",
              "options": {
                "values": [
                  "user"
                ]
              }
            }
          }
        },
        {
          "id": "read-swagger",
          "description": "Allows to service swagger.",
          "resources": [
            "/swagger<.+>"
          ],
          "actions": [
            "api:read"
          ],
          "effect": "allow",
          "subjects": [
            "<.+>"
          ]
        }
      ]
    }
  }

...

Configuration properties:

  • keysDir - is a directory containing the platfrom private/public keys
  • ignorePatterns - means ignore security for these patterns
  • jwt - contains configuration properties for JWT security.
  • saml - contains configuration properties for SAML security.
  • oauth2 - contains configuration properties for OAuth2 security.
  • acl - contains ACLs policies

More details for internal implementation of Microkubes's security can be found in the package.

Python Library

microkubes-python provides tools for implementing security in your services that works with the security infrastructure provided by Microkubes.

The security library offers python API for setting up security based on yor needs. It comes with couple of security providers out-of-the-box:

  • JWT provider - can decode and validate JWTs generated by Microkubes jwt-issuer service
  • OAuth2 provider - can decode and validate OAuth2 tokens generated by the OAuth2 Authorization Server in Microkubes.

The security is configured as a chain of providers, so you can have more than one type on any endpoint in your service.

The main entrypoint for this is the SecurityChain. When a new request is made by the client, the SecurityChain passes it to every provider in the chain. Every provider attempts to authenticate and authorize the request. If some of the providers is successful, then Auth object is generated and set in the SecurityContext.

An example of setting up security chain:

from microkubes.security import (SecurityChain,
                                 ThreadLocalSecurityContext,
                                 JWTSProvider,
                                 is_authenticated_provider,
                                 KeyStore)

store = KeyStore(dir_path='./keys')  # we need key store with the RSA keys so we can validate the JWT signature
context = ThreadLocalSecurityContext()  # Keeps the Auth in thread-local, which is fine for tests, but should be avoided for production

chain = (SecurityChain(context).
         provider(JWTSecurityProvider(key_store=store)).  # Security chain with JWT auth
         provider(is_authenticated_provider))  # check if the request has been authenticated


# assuming we receive an HTTP request, the processing of the request can be done like this:
    # try:
    #     chain.execute(context, request, response)
    # except SecurityException serr:
    #     response.status_code = serr.status_code
    #     response.write_json({"code": serr.status_code, "message": str(serr)})


# then we can access the context to get the Auth object:

auth = context.get_auth()

Security with Flask services

You can secure your Flask services by using the convenience security builder for Flask. You need to create new Security and then use Security.secured decorator on your endpoints.

By using the example in Flask above, we can secure the action by setting up a full security chain:

import os
from flask import Flask
from microkubes.gateway import KongGatewayRegistrator
from microkubes.security import FlaskSecurity


app = Flask(__name__)
registrator = KongGatewayRegistrator(os.environ.get("API_GATEWAY_URL", "http://localhost:8001"))  # Use the Kong registrator for Microkubes

# set up a security chain
sec = (FlaskSecurity().
        keys_dir("./keys").   # set up a key-store that has at least the public keys from the platform
        jwt().                # Add JWT support
        oauth2().             # Add OAuth2 support
        build())              # Build the security for Flask




# Self-registration on the API Gateway must be the first thing we do when running this service.
# If the registration fails, then the whole service must terminate.
registrator.register(name="hello-service",                  # the service name.
                     paths=["/hello"],                      # URL pattern that Kong will use to redirect requests to out service
                     host="hello-service.services.consul",  # The hostname of the service.
                     port=5000)                             # Flask default port. When redirecting, Kong will call us on this port.


@app.route("/hello")
@sec.secured   # this action is now secure
def hello():
    auth = sec.context.get_auth()  # get the Auth from the security context
    return "Hello %s from Flask service on Microkubes" % auth.username

More details for internal implementation of Microkubes's security can be found in the library.

There aren’t any published security advisories