Apcera Policy for ACME Co Using Policy Variables

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

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

Policy Variables

In traditional policy the data inputs and execution logic are combined. Policy variables separate policy input data from the policy rules using variables and data tables. Before proceeding, familarize yourself with policy variables.

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.

Example policy: authLDAP

The following sample authLDAP.pol policy document has a policy that allows LDAP group, "dev-group" members to log in, and setting their default namespace to begines with /dev/sandbox. Also, it assigns the "dev-group" members to the dev role. See Writing policy to support LDAP login for details on LDAP authentication.

// Let's LDAP Auth Server to issue a token to those "dev-group" members
// Assigning the LDAP common name (CN) to be the Apcera user name
on auth::/oauth2/http {
  if (user->group == "dev-group") {
    permit issue
    name user->name
  }
}

// LDAP group whitelisting
on auth::/ldap {
  { group.allow "dev-group" }
}

// Setting the default namespace prefix to be /dev/sandbox/
// ex. The default namespace for "james" will be /dev/sandbox/james
on auth::/ {
  if (user->group == "dev-group") {
    defaultNamespacePrefix "dev/sandbox/"
  }
}

// Assigning "dev-group" members to "dev" role on all resource types
on all::/ {
  if (query->target fqnMatch "*::/" && user->group == "dev-group") {
    role "dev"
  }
}

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. (Note that *::/ is supported for the fqnMatch to match on all resource types except the policy::/ and policydoc::/ relams.) At this point, a role named "dev" was created, but it has no operational meaning.

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

Developers should have a full permissions on their dedicated sandbox so that they can deploy workloads including applications from source code, Docker images, and bare container instances (capsules) without affecting others.

The following sample devRole.pol policy document defines the permissions for dev role.

// Pillar 1 & 3: Workload composition and connectivity
// Give full permissions on his/her own sandbox except for policy and policydoc
on all::/dev/sandbox/[name] {
  if (query->target fqnMatch "*::/dev/sandbox/[name]" && auth_server@apcera.me->name == [name]) {
    permit all
  }
}

// Optional policy demonstrating package resolution for Java developers
on job::/dev/sandbox/ {
  { package.retire "package::/apcera/pkg/runtimes::openjdk-1.6" }
  if (dependency equals runtime.java) {
     package.default "package::/apcera/pkg/runtimes::openjdk-1.8"
  }
  if (role == "dev")
  { docker.allow "*" }
}

// Pillar 2: Workload resources
on quota::/dev/sandbox/[name] {
  if (auth_server@apcera.me->name == [name])
  {
    total.memory 1GB
    total.disk 5GB
    max_instances 100
  }
}

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

In addition, the developers have a limited permissions to the production resources. For example, the developers can view the list of services in production, but they cannot bind jobs to it. The following sample systemDataTableRolePermissions.pol defines RolePermissions policy variable (PV) containing three fields (role, fqn, and permits). See Using Policy Variables for details.

// role = role name
// fqn = policy realm FQN
// permits = list of permissions

on variables::/  {
  system policy variable {
    RolePermissions (role, fqn, permits) {
      { "dev", "job::/prod", read }
      { "dev", "package::/prod", read }
      { "dev", "service::/prod", read }
      { "dev", "network::/prod", read }
      { "dev", "provider::/prod", read }
    }
  }
}

This data table defines that the dev role has read only permission on job::/prod, package::/prod, service::/prod, network::/prod, and provider::/prod realms.

The benefit of using policy variables is that you can easily scale the policy by adding another row of data using the data tables editor.

Alt text

You can add the following policy in devRole.pol to read the realm, role, and permit values from the RolePermissions PV to set appropriate level of permissions.

// Limited permissions on the production namespace
//  Retrieve claim values from RolePermissions PV
on all::/ {
  if (query->target fqnMatch PV->RolePermissions.fqn && role == PV->RolePermissions.role) {
    permit PV->RolePermissions.permits
  }
}

Example policy: globalPermits

Apcera provides several resources at the system level that developers need to be able to access. It is necessary to include policies that grant non-admin users sufficient permissions to deploy workloads in their sandboxed namespaces.

The following sample systemDataTableGlobalPermissions.pol defines the GlobalPermissions PV listing the realms of Apcera provided resources and its corresponding permit:

on variables::/ {
  system policy variable {
    GlobalPermissions (fqn, permissions) {
      {"audit::/", read}
      {"cluster::/", read}
      {"job::/apcera/service-gateways", read}
      {"job::/apcera/stagers", [read, use]}
      {"package::/apcera/pkg", [read, use]}
      {"provider::/apcera", read}
      {"service::/apcera", [read, bind]}
      {"stagpipe::/apcera", [read, use]}
      {"gateway::/apcera/service-gateways", use}
    }
  }
}

The following sample globalPermits.pol retrieves the realm FQN and its corresponding permissions from the GlobalPermissions PV.

on all::/ {
    if(query->target fqnMatch PV->GlobalPermissions.fqn) {
        permit PV->GlobalPermissions.permissions
    }
}

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 LDAP 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 this example, we enable the ldap_basic auth identity providers in the chef.continuum.auth_server.identity block.

The chef.continuum.auth_server.identity.<type>.users block sets the initial cluster users. For ldap_basic, you provide the parameters to configure your organization's LDAP server.

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": {
        "ldap_basic": {
          "enabled": true,
          "model_name": "basic",
          "search_dn": "cn=apcera_bind,ou=service_accounts,dc=acme,dc=com",
          "search_passwd": "pass@word1",
          "base_dn": "ou=people,dc=acme,dc=com",
          "group_base_dn": "ou=groups,dc=acme,dc=com"
          "uri": "ldaps://172.99.163.1d",
          "port": "636",
          "users": [
              "devopsuser1@acme.com",
              "opsuser1@acme.com",
              "adminuser1@acme.com"
            ]
         },
      },
      "admins": ["opsuser1@acme.com", "adminuser1@acme.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 LDAP auth user, and assigns a unique name:

on auth::/oauth2/http {
  if (user->email == "adminuser1@acme.com") {
    name user->name
    permit issue
  }
}

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

on auth::/oauth2/http {
  if (user->group == "apc.developers") {
    name user->name
    permit issue
  }
}

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 systemDataTableAssignedRoles.pol policy variable lists one or more users, and assigns them to the admin role for each resource. Note that the assignment of the admin role is done at the root level for each realm, meaning access to all resources in the cluster.

on variables::/ {
  system policy variable {
    AssignedRoles (userName, role, resource) {
      { "opsuser1@acme.com", "admin", ["*::/", "policy::/", "policydoc::/"] }
      { "adminuser1@acme.com", "admin", ["*::/", "policy::/", "policydoc::/"] }
    }
  }
}

The system-provided adminUsers.pol policy document completes the assignment of users to a role.

// The AssignedRoles data table drives which users are admins. Additional admins
// can be added by updating systemDataTableAssignedRoles.pol.

on all::/ {
  if (query->target fqnMatch PV->AssignedRoles.resource &&
      auth_server@apcera.me->name == PV->AssignedRoles.userName) {
        role PV->AssignedRoles.role
  }
}

To assign additional users to the admin role, simply modify the AssignedRoles PV by adding rows.

rolePermissions

The system-provided systemDataTableDefaultAdminRole.pol PV defines rows of data which maps all permission to the admin role at the root realm for each resource type.

// DO NOT EDIT: THIS IS A SYSTEM POLICY AND IT WILL BE OVERWRITTEN BY THE APCERA PLATFORM.

on variables::/ {
  system policy variable {
    DefaultAdminRole (role, fqn, capabilities) {
      {"admin", "audit::/",       "all"}
      {"admin", "cluster::/",     "all"}
      {"admin", "gateway::/",     "all"}
      {"admin", "job::/",         "all"}
      {"admin", "network::/",     "all"}
      {"admin", "package::/",     "all"}
      {"admin", "policy::/",      "all"}
      {"admin", "policydoc::/",   "all"}
      {"admin", "principal::/",   "all"}
      {"admin", "provider::/",    "all"}
      {"admin", "route::/",       "all"}
      {"admin", "sempiperule::/", "all"}
      {"admin", "service::/",     "all"}
      {"admin", "stagpipe::/",    "all"}
    }
  }
}

The system-provided rolePermissions.pol policy document reads the role name, realm FQN, and its permit from the DefaultAdminRole PV.

    // DO NOT EDIT: THIS IS A SYSTEM POLICY AND IT WILL BE OVERWRITTEN BY THE APCERA PLATFORM.

    on all::/ {
      if (query->target fqnMatch PV->DefaultAdminRole.fqn && role == PV->DefaultAdminRole.role) {
          permit PV->DefaultAdminRole.capabilities
      }
    }

clusterPermissions

The system-provided systemDataTableDefaultPermissions.pol defines DefaultPermissions PV which maps all permissions for each resource type. Note that the permissions available are different for each resource type.

// DO NOT EDIT: THIS IS A SYSTEM POLICY AND IT WILL BE OVERWRITTEN BY THE APCERA PLATFORM.

on variables::/ {
  system policy variable {
    DefaultPermissions (resource, from, to) {
      {"audit::/",       "all", [read]}
      {"cluster::/",     "all", [read, update]}
      {"gateway::/",     "all", [use, promote]}
      {"job::/",         "all", [create, read, update, delete, start, stop, map, ssh, link, promote, bind, join]}
      {"network::/",     "all", [create, join, read, delete]}
      {"package::/",     "all", [create, read, update, delete, use]}
      {"policy::/",      "all", [read, update]}
      {"policydoc::/",   "all", [create, read, update, delete]}
      {"principal::/",   "all", [create, read, update, delete]}
      {"provider::/",    "all", [create, read, update, delete]}
      {"route::/",       "all", [map]}
      {"sempiperule::/", "all", [create, read, delete]}
      {"service::/",     "all", [create, read, update, delete, bind]}
      {"stagpipe::/",    "all", [create, read, update, delete, use]}
    }
  }
}

The systemDataTableAccessAuditLogging.pol policy document defines AccessAuditLogging policy variable which sets the audit log level to be none for all resource types. And the clusterPermissions.pol policy document references DefaultPermissions to define what permit all means. It also reads audit log level from AccessAuditLogging to define the default behavior.

    // Define capabilities of permit categories defined in the DefaultPermissions Policy Variable.
    on all::/ {
      if (query->target fqnMatch PV->DefaultPermissions.resource && permit == PV->DefaultPermissions.from) {
        permit PV->DefaultPermissions.to
      }
      if (query->target fqnMatch PV->AccessAuditLogging.resource ) {
        log PV->AccessAuditLogging.accessEvents
      }
    }

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 == "adminuser1") {
    defaultNamespace "/sandbox/adminuser1"
  }
}

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

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

on auth::/ {
  if (role == "dev") {
    defaultNamespacePrefix "dev/"
  }
  if (role == "admin") {
    defaultNamespacePrefix "/admin/cluster-"
  }
}

The above results in a default namespaces of /dev/devuser1 for a user devuser1, and /admin/cluster-adminuser1 for adminuser1.

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