Apcera Policy for ACME Co

This document provides example Apcera Policy for a fictitious company called ACME.

Refer to the Apcera Policy documentation for full details on the policy language and syntax, as well as additional examples.

Policy overview

Apcera policy is a series of statements in a policy language that dictates user authorizations, controls resource allocation, and enables interprocess communication and access.

Key policy language concepts

  • Deny all. To do anything in the system, a user or application must have policy that permits it.
  • Permissions are resource specific. There are 15 resource types against which you can write policy.
  • FQN (Fully Qualified Name). How resources are identified in the system: resourceType::/<namespace>::<local-name>.

Policy objects

  • Policy document. File named *.pol that contains 1 or more policies.
  • Policy realm. Statement in the form on FQN that defines the scope of a single policy.
  • Policy rules. If/then statements that grant one or more claims or permissions.

Policy and the Four Pillars of Security Management

Permissions to use cluster resources are granted using policy and can be grouped into four pillars:

  • Pillar I — Workload composition and deployment
  • Pillar II — Workload resource management (quota)
  • Pillar III — Workload connectivity and networking
  • Pillar IV — Workload scheduling and placement

Alt text

Example policy

What follows are illustrations of how role-based policy is crafted to implement each of the four pillars. Running these examples requires that a cluster be set up with user identities as described in the "Authentication and User Identity" appendix section of this document.

Policy role definition

A role is defined by the claims declared in policy rules, using the conditional syntax shown here:

on <resourceType>::/ {
  if (role == "roleName") 
  { 
    claimName claimValue1, [claimValue2], [claimValueN]
  }
}

Example policy: devRole

A typical developer will need to be able to deploy workloads to the platform, including applications from source code, Docker images, and bare container instances (capsules). To create jobs, developers need to be able to create and use packages and stagers. In addition, developers will likely want to be able to connect their applications to services such as databases. As such, they will need access to services, providers, and gateways. Typically you will want to put reasonable limitations on how much resources a developer can use. All of this is accomplished using policy.

The following sample devRole.pol policy document defines the dev role and its permissions by pillar. See Policy Permissions for details on what effect each claimName has and the options for claimValues. Note also that this policy assumes each dev user is authenticated and issued an access token. See the "Authentication and User Identity" appendix for details.

// Pillar 1: Workload composition
on job::/ {
  if (role == "dev") { 
    permit create, read, update, delete 
    permit start, stop, map, ssh
    docker.allow "*"
  }
}
on package::/ {
  if (role == "dev") { 
    permit create, read, update, delete
    permit use 
  }
}
on route::/ {
  if (role == "dev") {
    permit map
  }
}
// Optional policy demonstrating package resolution for Java developers
on job::/ {
  { package.retire "package::/apcera/pkg/runtimes::openjdk-1.6" }
  if (dependency equals runtime.java) {
     package.default "package::/apcera/pkg/runtimes::openjdk-1.8"
  }
}

// Pillar 2: Workload resources
// NOTE: Must use actual NAME value in the realm
on quota::/sandbox/dev/NAME {
  { 
    total.memory 1GB
    total.disk 5GB
    max_instances 100
  }
}

// Pillar 3: Workload connectivity
on job::/ {
  if (role == "dev") { 
    permit link, bind, promote, join
  }
}
on service::/ {
  if (role == "dev") {
    permit create, read, update, delete
    permit bind
  }
}
on gateway::/ {
  if (role == "dev") {
    permit use, promote
  }
}
on network::/ {
  if (role == "dev") {
    permit create, join, read, delete
  }
}
sempiperule::/ {
  if (role == "dev") {
    permit read, use 
  }
}

// Pillar 4: Workload placement
on job::/ {
  if (role == "dev") {
    schedulingTag.soft devImPool 
  }
}

Example policy: devUsers

The following sample devUsers.pol policy document has policy that maps users to the dev role. Note that the policy realm for each role assignment is specified at the namespace level thereby limiting the permission grants to that sandboxed namespace.

// Default namespace prefix giving each developer a 
// default namespace in the form /sandbox/dev/NAME
on auth::/ {
   if (role == dev) {
      defaultNamespacePrefix "sandbox/dev/"
   }
}

// Adding user to dev role for each realm 
on auth::/ {
   if (auth_server@apcera.me->name == "john.doe@acme.com") {
      role dev
   }
}
on audit::/sandbox/dev/john.doe {
  if (auth_server@apcera.me->name == "john.doe@acme.com") 
    { role dev }
}
on cluster::/sandbox/dev/john.doe {
  if (auth_server@apcera.me->name == "john.doe@acme.com") 
    { role dev }
}
on job::/sandbox/dev/john.doe {
  if (auth_server@apcera.me->name == "john.doe@acme.com") 
    { role dev }
}

// etc. for all root realms except policy::/ and policydoc::/

Typically you will grant non-admin users role-based permissions over all cluster resources except policy::/ and policydoc::/. Policy permissions should be restricted to admins and designated policy authors.

Example policy: globalPermits

Apcera provides several resources at the system level that developers need to be able to access. Including global policy with the example dev policies gives non-admin users sufficient permissions to deploy workloads across the four pillars in their sandboxed namespaces.

The sample globalPermits.pol policy document grants access to Apcera-provided resources.

// Pillar I: Workload composition
on job::/apcera/stagers {
  { permit read, use }
}
on package::/apcera/pkg {
  { permit read, use }
}
stagpipe::/apcera {
  { permit read, use }
}
// Pillar II: Workload resources
on quota::/ {
  { max.package.size 4GB }
}
// Pillar III: Workload connectivity
provider::/apcera {
  { permit read }
}
service::/apcera {
  { permit read, bind }
}

Note that workload placement (Pillar IV) is typically defined at role or user level as shown above.

Appendix: Authentication and User Identity

This appendix briefly describes how users can authenticate with the system. The appendix demonstrates deploying a cluster with default policy examples.

Identity providers

Apcera supports several identity providers. This sample policy demonstrates Basic and Google auth.

Bootstrapping users

When you configure your cluster using the cluster.conf file, you define the identity provider and bootstrap the system with an initial set of users.

In the example, we enable the google and basic auth identity providers in the chef.continuum.auth_server.identity block.

The chef.continuum.auth_server.identity.<type>.users block we specify the initial cluster users. For basic you provide the user name and password. For google you provide the keys for your Google Developer Project.

Including a named user in the chef.continuum.auth_server.admins block adds the user to the admin role (described later).

chef: {
  "continuum": {
    "auth_server": {
      "identity": {
        "google": {
          "users": ["apcerauser@gmail.com", <someone-else@gmail.com>],
          "enabled": true,
          "client_id": "1035599215705-knc5h3ipallgeuucl5pbgqtjdbdvjoft.apps.googleusercontent.com",
          "client_secret": "_avb837RTtqIy6WNCwYpDA8E",
          "web_client_id": "1035599215705-d4qavosl40jduohlf7mfkmph6pviq3lc.apps.googleusercontent.com"
        },
        "basic": {
          "enabled": true,
          "users": [
            {
              "name": "admin",
              "password": "admin"
            }
            <{
              "name": "anotheruser",
              "password": "anotheruser"
            }>
          ]
        },
      },
      "admins": ["admin@apcera.me", "adminuser@gmail.com"],
    }
  }
}

Authenticating users

Once a user is bootstrapped, the user is issued an access token using policy. Note that the following policy is generated automatically for users bootstrapped via cluster.conf.

The following policy issues an access token to the Basic auth user named in cluster.conf:

on auth::/oauth2/http {
  if (auth_server@apcera.me->name == "admin@apcera.me") {
    permit issue
  }
}

The following policy issues an access token to Google auth user, and assigns a unique name:

on auth::/oauth2/http {
  if (Google->email == "apcerauser@gmail.com") {
    name "apcerauser@gmail.com"
    permit issue
  }
}

To add a new google auth user, you simply add policy that issues each new user an access token and unique name. For example:

on auth::/oauth2/http {
  if (Google->email == "anotheruser@gmail.com") {
    name "anotheruser@gmail.com"
    permit issue
  }
}

To add a new basic auth user, you must update cluster.conf with the user name and password and redeploy the cluster, then add policy that issues an access token.

Note that you can also issue an access token to a job:

on job::/sandbox/user::myjob { 
  { permit issue }
}

Default admin role

Apcera provides default policy for the admin role. Bootstrapped users named in the chef.continuum.auth_server.admins block of cluster.conf are added to the admin role. You can manually add additional admin users using policy.

adminUsers

The system-provided adminUsers.pol policy document has policy that adds one or more users to the admin role for each realm. Note that the assignment of the admin role is done at the root level for each realm, meaning full access to resources in that realm.

To add another user to the admin role, simply create a rule for each user you want to add for each realm. For example:

on audit::/ {
  if (auth_server@apcera.me->name == "admin@apcera.me") 
    { role admin }
  if (auth_server@apcera.me->name == "apcerauser@gmail.com") 
    { role admin }
  if (auth_server@apcera.me->name == "anotheruser@gmail.com") 
    { role admin }
}
on cluster::/ {
  if (auth_server@apcera.me->name == "admin@apcera.me") 
    { role admin }
  if (auth_server@apcera.me->name == "apcerauser@gmail.com") 
    { role admin }
  if (auth_server@apcera.me->name == "anotheruser@gmail.com") 
    { role admin }
}
on job::/ {
  if (auth_server@apcera.me->name == "admin@apcera.me") 
    { role admin }
  if (auth_server@apcera.me->name == "apcerauser@gmail.com") 
    { role admin }
  if (auth_server@apcera.me->name == "anotheruser@gmail.com") 
    { role admin }
}

// etc. for all root realms

rolePermissions

The system-provided and read-only rolePermissions.pol policy document maps the all permission to the admin role at the root realm for each resource type.

audit::/ {
  if (role == "admin") 
    { permit all }
}

cluster::/ {
  if (role == "admin") 
    { permit all }
}

job::/ {
  if (role == "admin") 
    { permit all }
}

// etc. for all root realms

clusterPermissions

The system-provided and read-only clusterPermissions.pol policy document has policy that defines all permissions for each resource type. Note that the permissions available are different for each resource type.

audit::/ {
  if (permit == all) {
    permit read
  }
}

cluster::/ {
  if (permit == all) {
    permit read, update
  }
}

job::/ {
  if (permit == all) {
    permit create, read, update, delete
    permit start, stop, map, ssh, link, promote, bind
  }
}

// etc. for all root realms

Namespaces

When a user or application is authenticated (issued an access token), the system automatically generates a default namespace for the user under the in the form /sandbox/, for example `/sandbox/acperauser`. If the automatically generated namespace needs to be changed you can customize its behavior using the following.

Specify an explicit namespace to use with the defaultNamespace claim. This overrides the automatic namespace generation when the rule matches.

on auth::/ {
  if (auth_server@apcera.me->name == "admin@apcera.me") {
    defaultNamespace "/sandbox/administrator"
  }
}

The above results in a default namespace of /sandbox/administrator.

Specify the path part of the namespace with the defaultNamespacePrefix claim. This overrides the standard path of "/sandbox/".

on auth::/ {
  if (auth_server@apcera.me->name == "apcerauser@gmail.com") {
    defaultNamespacePrefix "user/"
  }
  if (auth_server@apcera.me->name == "admin@apcera.me") {
    defaultNamespacePrefix "/admin/cluster-"
  }
}

The above results in a default namespaces of /user/apcerauser and /admin/cluster-admin.

For more details on namespace generation, see Default Namespace Policy Example