Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance authz interface for policies #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/server/middleware/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ func NewAuthorizationMiddleware(plugins []authorization.Plugin) Middleware {
}

rw := authorization.NewResponseModifier(w)
newCtx := context.WithValue(ctx, "policies", authCtx.Policies())

if err := handler(ctx, rw, r, vars); err != nil {
if err := handler(newCtx, rw, r, vars); err != nil {
logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
Expand Down
8 changes: 8 additions & 0 deletions api/server/router/container/container_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
version := httputils.VersionFromContext(ctx)
adjustCPUShares := version.LessThan("1.19")

// set container policies associated by authz plugin(s)
policies, ok := ctx.Value("policies").([]container.Policy)
if !ok {
logrus.Debugf("incorrect type for policy map %T, expected %T", ctx.Value("policies"), config.Policies)
policies = []container.Policy{}
}
config.Policies = policies

ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
Name: name,
Config: config,
Expand Down
13 changes: 8 additions & 5 deletions docs/extend/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ same is true for callers using Docker's remote API to contact the daemon. If you
require greater access control, you can create authorization plugins and add
them to your Docker daemon configuration. Using an authorization plugin, a
Docker administrator can configure granular access policies for managing access
to Docker daemon.
to Docker daemon. Additionally, an authorization plugin may also return policies
associated with the container to provide a managed environment for container execution.
These policies are enforced by the Docker daemon and/or supporting drivers.

Anyone with the appropriate skills can develop an authorization plugin. These
skills, at their most basic, are knowledge of Docker, understanding of REST, and
Expand All @@ -39,7 +41,10 @@ need to restart the Docker daemon to add a new plugin.
An authorization plugin approves or denies requests to the Docker daemon based
on both the current authentication context and the command context. The
authentication context contains all user details and the authentication method.
The command context contains all the relevant request data.
The command context contains all the relevant request data. When a request is approved
the authorization plugin optionally returns a set of associated policies for container
execution. These policies are expected to be enforced by the docker daemon or
the supporting drivers.

Authorization plugins must follow the rules described in [Docker Plugin API](plugin_api.md).
Each plugin must reside within directories described under the
Expand Down Expand Up @@ -162,6 +167,7 @@ should implement the following two methods:
"Allow": "Determined whether the user is allowed or not",
"Msg": "The authorization message",
"Err": "The error message if things go wrong"
"Policies" : "The policies associated with the allowed request."
}
```
#### /AuthzPlugin.AuthZRes
Expand Down Expand Up @@ -190,9 +196,6 @@ should implement the following two methods:
"Allow": "Determined whether the user is allowed or not",
"Msg": "The authorization message",
"Err": "The error message if things go wrong",
"ModifiedBody": "Byte array containing a modified body of the raw HTTP body (or nil if no changes required)",
"ModifiedHeader": "Byte array containing a modified header of the HTTP response (or nil if no changes required)",
"ModifiedStatusCode": "int containing the modified version of the status code (or 0 if not change is required)"
}
```

Expand Down
8 changes: 8 additions & 0 deletions pkg/authorization/api.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package authorization

import "github.com/docker/engine-api/types/container"

const (
// AuthZApiRequest is the url for daemon request authorization
AuthZApiRequest = "AuthZPlugin.AuthZReq"
Expand Down Expand Up @@ -39,6 +41,9 @@ type Request struct {

// ResponseHeaders stores the response headers sent to the docker daemon
ResponseHeaders map[string]string `json:"ResponseHeaders,omitempty"`

// Policies stores a list of policies associated/identified with given request till this point
Policies []container.Policy `json:"Policies,omitempty"`
}

// Response represents authZ plugin response
Expand All @@ -51,4 +56,7 @@ type Response struct {

// Err stores a message in case there's an error
Err string `json:"Err,omitempty"`

// Policies stores a list of policies associated with the authorized request
Policies []container.Policy `json:"Policies,omitempty"`
}
12 changes: 12 additions & 0 deletions pkg/authorization/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/engine-api/types/container"
)

const maxBodySize = 1048576 // 1MB
Expand Down Expand Up @@ -74,6 +75,7 @@ func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
RequestURI: ctx.requestURI,
RequestBody: body,
RequestHeaders: headers(r.Header),
Policies: []container.Policy{},
}

for _, plugin := range ctx.plugins {
Expand All @@ -86,6 +88,11 @@ func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {

if !authRes.Allow {
return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg)
} else {
// store the policies returned by authz plugin.
for _, p := range authRes.Policies {
ctx.authReq.Policies = append(ctx.authReq.Policies, p)
}
}
}

Expand Down Expand Up @@ -119,6 +126,11 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
return nil
}

// Policies returns the policies associated with the authz context for a request
func (ctx *Ctx) Policies() []container.Policy {
return ctx.authReq.Policies
}

// drainBody dump the body (if it's length is less than 1MB) without modifying the request state
func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
bufReader := bufio.NewReaderSize(body, maxBodySize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ type Config struct {
OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile
Labels map[string]string // List of labels set to this container
StopSignal string `json:",omitempty"` // Signal to stop a container
Policies []Policy // List of policy values
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package container

import (
"encoding/json"
"fmt"
)

const (
NetworkFirewallAttrType = "firewall"
NetworkBandwidthAttrType = "bandwidth"
NetworkCOSAttrType = "cos"
NetworkVendorSpecificAttrType = "vendor-specific"
)

var AllowedPolicyAttributes = map[string]bool{
NetworkFirewallAttrType: true,
NetworkBandwidthAttrType: true,
NetworkCOSAttrType: true,
NetworkVendorSpecificAttrType: true,
}

func InvalidPolicyAttributeStringError(rcvd, exptd string) error {
return fmt.Errorf("invalid policy attribute string: %q. Expected: %q", rcvd, exptd)
}

type firewallRule struct {
direction string
action string
protocol string
port string
peerGroupId string
peerCIDR string
}

// NetworkFirewallAttr defines a security policy that is collection of
// actions for traffic matching L3/L4 protocol and L4 port
type NetworkFirewallAttr struct {
rules []firewallRule
groupId string
}

func NewNetworkFirewallAttr() *NetworkFirewallAttr {
return &NetworkFirewallAttr{rules: []firewallRule{}}
}

func (fw *NetworkFirewallAttr) AddRule(direction, action, protocol, port, peerGroupId, peerCIDR string) {
fw.rules = append(fw.rules, firewallRule{
direction: direction,
action: action,
protocol: protocol,
port: port,
peerGroupId: peerGroupId,
peerCIDR: peerCIDR,
})
}

func (fw *NetworkFirewallAttr) Type() string {
return NetworkFirewallAttrType
}

func (fw *NetworkFirewallAttr) MarshalJSON() ([]byte, error) {
// local type for marshalling
type forJSON struct {
Direction string `json:"direction"`
Action string `json:"action"`
Protocol string `json: "protocol"`
Port string `json:"port"`
PeerGroupId string `json:"peerGroupId"`
PeerCIDR string `json:"peerCIDR"`
}
rules := []forJSON{}

for _, r := range fw.rules {
rules = append(rules, forJSON{
Direction: r.direction,
Action: r.action,
Protocol: r.protocol,
Port: r.port,
PeerGroupId: r.peerGroupId,
PeerCIDR: r.peerCIDR,
})
}

return json.Marshal(struct {
Type string `json:"type"`
Data struct {
Rules []forJSON `json:"rules"`
GroupId string `json:"groupId"`
} `json:"data"`
}{
Type: NetworkFirewallAttrType,
Data: struct {
Rules []forJSON `json:"rules"`
GroupId string `json:"groupId"`
}{
Rules: rules,
GroupId: fw.groupId,
},
})
}

func (fw *NetworkFirewallAttr) UnmarshalJSON(in []byte) error {
// local type for marshalling
type forJSON struct {
Type string `json:"type"`
Data struct {
GroupId string `json:"groupId"`
Rules []struct {
Direction string `json:"direction"`
Action string `json:"action"`
Protocol string `json: "protocol"`
Port string `json:"port"`
PeerGroupId string `json:"peerGroupId"`
PeerCIDR string `json:"peerCIDR"`
} `json:"rules"`
} `json:"data"`
}

val := forJSON{}
if err := json.Unmarshal(in, &val); err != nil {
return err
}

if val.Type != NetworkFirewallAttrType {
return InvalidPolicyAttributeStringError(val.Type, NetworkFirewallAttrType)
}

fw.groupId = val.Data.GroupId
for _, r := range val.Data.Rules {
fw.AddRule(r.Direction, r.Action, r.Protocol, r.Port, r.PeerGroupId, r.PeerCIDR)
}
return nil
}

// NetworkBandwidthAttr defines a policy that controls the bandwidth
// available for traffic originating from the endpoint in a network
type NetworkBandwidthAttr string

func NewNetworkBandwidthAttr(bw string) *NetworkBandwidthAttr {
val := NetworkBandwidthAttr(bw)
return &val
}

func (bw *NetworkBandwidthAttr) Type() string {
return NetworkBandwidthAttrType
}

func (bw *NetworkBandwidthAttr) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Type string `json:"type"`
Data string `json:"data"`
}{
Type: NetworkBandwidthAttrType,
Data: string(*bw),
})
}

func (bw *NetworkBandwidthAttr) UnmarshalJSON(in []byte) error {
val := struct {
Type string `json:"type"`
Data string `json:"data"`
}{}

if err := json.Unmarshal(in, &val); err != nil {
return err
}

if val.Type != NetworkBandwidthAttrType {
return InvalidPolicyAttributeStringError(val.Type, NetworkBandwidthAttrType)
}

*bw = NetworkBandwidthAttr(val.Data)
return nil
}

// NetworkCOSAttr defines a policy that controls the class of service
// applied for traffic originating from the endpoint in a network
type NetworkCOSAttr int

func NewNetworkCOSAttr(cos int) *NetworkCOSAttr {
val := NetworkCOSAttr(cos)
return &val
}

func (cos *NetworkCOSAttr) Type() string {
return NetworkCOSAttrType
}

func (cos *NetworkCOSAttr) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Type string `json:"type"`
Data int `json:"data"`
}{
Type: NetworkCOSAttrType,
Data: int(*cos),
})
}

func (cos *NetworkCOSAttr) UnmarshalJSON(in []byte) error {
val := struct {
Type string `json:"type"`
Data int `json:"data"`
}{}

if err := json.Unmarshal(in, &val); err != nil {
return err
}

if val.Type != NetworkCOSAttrType {
return InvalidPolicyAttributeStringError(val.Type, NetworkCOSAttrType)
}

*cos = NetworkCOSAttr(val.Data)
return nil
}

// NetworkVendorSpecificAttr defines a vendor specific policy label that allows
// to apply vendor specific polices for traffic originating from the
// endpoint in a network
type NetworkVendorSpecificAttr string

func NewNetworkVendorSpecificAttr(vs string) *NetworkVendorSpecificAttr {
val := NetworkVendorSpecificAttr(vs)
return &val
}

func (vs *NetworkVendorSpecificAttr) Type() string {
return NetworkVendorSpecificAttrType
}

func (vs *NetworkVendorSpecificAttr) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Type string `json:"type"`
Data string `json:"data"`
}{
Type: NetworkVendorSpecificAttrType,
Data: string(*vs),
})
}

func (vs *NetworkVendorSpecificAttr) UnmarshalJSON(in []byte) error {
val := struct {
Type string `json:"type"`
Data string `json:"data"`
}{}

if err := json.Unmarshal(in, &val); err != nil {
return err
}

if val.Type != NetworkVendorSpecificAttrType {
return InvalidPolicyAttributeStringError(val.Type, NetworkVendorSpecificAttrType)
}

*vs = NetworkVendorSpecificAttr(val.Data)
return nil
}
Loading