From 839ce2d41e0ed3516b15ec191febf54d549f8882 Mon Sep 17 00:00:00 2001 From: Madhav Puri Date: Fri, 15 Jan 2016 04:20:54 -0800 Subject: [PATCH 1/2] modify authz plugin interface to allow passing policies from plugin to daemon Also hooked container create handler to set the policies in container config. Signed-off-by: Madhav Puri --- api/server/middleware/authorization.go | 3 +- .../router/container/container_routes.go | 8 ++ docs/extend/authorization.md | 13 ++- pkg/authorization/api.go | 8 ++ pkg/authorization/authz.go | 16 ++++ .../engine-api/types/container/config.go | 1 + .../engine-api/types/container/policy.go | 92 +++++++++++++++++++ 7 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 vendor/src/github.com/docker/engine-api/types/container/policy.go diff --git a/api/server/middleware/authorization.go b/api/server/middleware/authorization.go index cbfa99e7b3071..efd2eb0311fb8 100644 --- a/api/server/middleware/authorization.go +++ b/api/server/middleware/authorization.go @@ -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 } diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index 016e00f05b7b3..cadae941080f0 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -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").(map[container.PolicyType]string) + if !ok { + logrus.Debugf("incorrect type for policy map %T, expected %T", ctx.Value("policies"), config.Policies) + policies = make(map[container.PolicyType]string) + } + config.Policies = policies + ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ Name: name, Config: config, diff --git a/docs/extend/authorization.md b/docs/extend/authorization.md index 3512c56ccd28d..53dce435848db 100644 --- a/docs/extend/authorization.md +++ b/docs/extend/authorization.md @@ -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 @@ -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 @@ -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 @@ -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)" } ``` diff --git a/pkg/authorization/api.go b/pkg/authorization/api.go index fc82c46b01aba..7c481fd1d60a6 100644 --- a/pkg/authorization/api.go +++ b/pkg/authorization/api.go @@ -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" @@ -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 map[container.PolicyType]string `json:"Policies,omitempty"` } // Response represents authZ plugin response @@ -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 map[container.PolicyType]string `json:"Policies,omitempty"` } diff --git a/pkg/authorization/authz.go b/pkg/authorization/authz.go index f703908649a1d..dd13c95612a08 100644 --- a/pkg/authorization/authz.go +++ b/pkg/authorization/authz.go @@ -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 @@ -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: make(map[container.PolicyType]string), } for _, plugin := range ctx.plugins { @@ -86,6 +88,15 @@ 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 k, v := range authRes.Policies { + if !k.IsValidType() { + logrus.Infof("authz plugin returned an invalid policy type: %d", k) + continue + } + ctx.authReq.Policies[k] = v + } } } @@ -119,6 +130,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() map[container.PolicyType]string { + 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) diff --git a/vendor/src/github.com/docker/engine-api/types/container/config.go b/vendor/src/github.com/docker/engine-api/types/container/config.go index b8747a50874c6..fbf4935e09b9f 100644 --- a/vendor/src/github.com/docker/engine-api/types/container/config.go +++ b/vendor/src/github.com/docker/engine-api/types/container/config.go @@ -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 PolicyMap // List of policy values } diff --git a/vendor/src/github.com/docker/engine-api/types/container/policy.go b/vendor/src/github.com/docker/engine-api/types/container/policy.go new file mode 100644 index 0000000000000..1213cab1a6938 --- /dev/null +++ b/vendor/src/github.com/docker/engine-api/types/container/policy.go @@ -0,0 +1,92 @@ +package container + +import ( + "encoding/json" + "fmt" +) + +type PolicyType int + +type PolicyMap map[PolicyType]string + +const ( + NetworkFireWallPolicy PolicyType = iota + NetworkPriorityPolicy + NetworkBandwidthPolicy + NetworkLoadbalancePolicy + StorageRatelimitPolicy + StorageSnapshotPolicy +) + +var AllowedPolicyTypes = map[PolicyType]string{ + NetworkFireWallPolicy: "network-firewall-policy", + NetworkPriorityPolicy: "network-priority-policy", + NetworkBandwidthPolicy: "network-bandwidth-policy", + NetworkLoadbalancePolicy: "network-loadbalance-policy", + StorageRatelimitPolicy: "storage-ratelimit-policy", + StorageSnapshotPolicy: "storage-snapshot-policy", +} + +var AllowedPolicyStrings = map[string]PolicyType{ + "network-firewall-policy": NetworkFireWallPolicy, + "network-priority-policy": NetworkPriorityPolicy, + "network-bandwidth-policy": NetworkBandwidthPolicy, + "network-loadbalance-policy": NetworkLoadbalancePolicy, + "storage-ratelimit-policy": StorageRatelimitPolicy, + "storage-snapshot-policy": StorageSnapshotPolicy, +} + +func (pt PolicyType) IsValidType() bool { + _, ok := AllowedPolicyTypes[pt] + return ok +} + +func (pt PolicyType) String() string { + str, ok := AllowedPolicyTypes[pt] + if !ok { + return "undefined" + } + return str +} + +func (pt PolicyType) MarshalJSON() ([]byte, error) { + if !pt.IsValidType() { + return nil, fmt.Errorf("unsupported policy type: %d", pt) + } + return json.Marshal(pt.String()) +} + +func (pt PolicyType) UnmarshalJSON(in []byte) error { + v, ok := AllowedPolicyStrings[string(in)] + if !ok { + return fmt.Errorf("unsupported policy string: %q", in) + } + pt = v + return nil +} + +func (pm PolicyMap) MarshalJSON() ([]byte, error) { + m := make(map[string]string) + for k, v := range pm { + if !k.IsValidType() { + return nil, fmt.Errorf("unsupported policy type: %d", k) + } + m[k.String()] = v + } + return json.Marshal(m) +} + +func (pm PolicyMap) UnmarshalJSON(in []byte) error { + m := make(map[string]string) + if err := json.Unmarshal(in, &m); err != nil { + return err + } + for k, v := range m { + pt, ok := AllowedPolicyStrings[k] + if !ok { + return fmt.Errorf("unsupported policy string: %q", in) + } + pm[pt] = v + } + return nil +} From d380239f168c3895c509769a69e90d63d7dc0444 Mon Sep 17 00:00:00 2001 From: Madhav Puri Date: Fri, 22 Jan 2016 04:17:56 -0800 Subject: [PATCH 2/2] Add policy constructs based on jainvipin's gist https://gist.github.com/jainvipin/8b1677f041534df576b2 Signed-off-by: Madhav Puri --- .../router/container/container_routes.go | 4 +- pkg/authorization/api.go | 4 +- pkg/authorization/authz.go | 12 +- .../engine-api/types/container/config.go | 2 +- .../container/network_policy_attribute.go | 257 ++++++++++++++++++ .../engine-api/types/container/policy.go | 121 ++++----- .../engine-api/types/container/policy_test.go | 166 +++++++++++ 7 files changed, 480 insertions(+), 86 deletions(-) create mode 100644 vendor/src/github.com/docker/engine-api/types/container/network_policy_attribute.go create mode 100644 vendor/src/github.com/docker/engine-api/types/container/policy_test.go diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index cadae941080f0..45f344689727d 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -358,10 +358,10 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo adjustCPUShares := version.LessThan("1.19") // set container policies associated by authz plugin(s) - policies, ok := ctx.Value("policies").(map[container.PolicyType]string) + 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 = make(map[container.PolicyType]string) + policies = []container.Policy{} } config.Policies = policies diff --git a/pkg/authorization/api.go b/pkg/authorization/api.go index 7c481fd1d60a6..9030432aacca6 100644 --- a/pkg/authorization/api.go +++ b/pkg/authorization/api.go @@ -43,7 +43,7 @@ type Request struct { ResponseHeaders map[string]string `json:"ResponseHeaders,omitempty"` // Policies stores a list of policies associated/identified with given request till this point - Policies map[container.PolicyType]string `json:"Policies,omitempty"` + Policies []container.Policy `json:"Policies,omitempty"` } // Response represents authZ plugin response @@ -58,5 +58,5 @@ type Response struct { Err string `json:"Err,omitempty"` // Policies stores a list of policies associated with the authorized request - Policies map[container.PolicyType]string `json:"Policies,omitempty"` + Policies []container.Policy `json:"Policies,omitempty"` } diff --git a/pkg/authorization/authz.go b/pkg/authorization/authz.go index dd13c95612a08..879f1d94b1a76 100644 --- a/pkg/authorization/authz.go +++ b/pkg/authorization/authz.go @@ -75,7 +75,7 @@ func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error { RequestURI: ctx.requestURI, RequestBody: body, RequestHeaders: headers(r.Header), - Policies: make(map[container.PolicyType]string), + Policies: []container.Policy{}, } for _, plugin := range ctx.plugins { @@ -90,12 +90,8 @@ func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error { return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg) } else { // store the policies returned by authz plugin. - for k, v := range authRes.Policies { - if !k.IsValidType() { - logrus.Infof("authz plugin returned an invalid policy type: %d", k) - continue - } - ctx.authReq.Policies[k] = v + for _, p := range authRes.Policies { + ctx.authReq.Policies = append(ctx.authReq.Policies, p) } } } @@ -131,7 +127,7 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error { } // Policies returns the policies associated with the authz context for a request -func (ctx *Ctx) Policies() map[container.PolicyType]string { +func (ctx *Ctx) Policies() []container.Policy { return ctx.authReq.Policies } diff --git a/vendor/src/github.com/docker/engine-api/types/container/config.go b/vendor/src/github.com/docker/engine-api/types/container/config.go index fbf4935e09b9f..ddbf143c287b3 100644 --- a/vendor/src/github.com/docker/engine-api/types/container/config.go +++ b/vendor/src/github.com/docker/engine-api/types/container/config.go @@ -35,5 +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 PolicyMap // List of policy values + Policies []Policy // List of policy values } diff --git a/vendor/src/github.com/docker/engine-api/types/container/network_policy_attribute.go b/vendor/src/github.com/docker/engine-api/types/container/network_policy_attribute.go new file mode 100644 index 0000000000000..0337c05435708 --- /dev/null +++ b/vendor/src/github.com/docker/engine-api/types/container/network_policy_attribute.go @@ -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 +} diff --git a/vendor/src/github.com/docker/engine-api/types/container/policy.go b/vendor/src/github.com/docker/engine-api/types/container/policy.go index 1213cab1a6938..c666754dbaa4b 100644 --- a/vendor/src/github.com/docker/engine-api/types/container/policy.go +++ b/vendor/src/github.com/docker/engine-api/types/container/policy.go @@ -5,88 +5,63 @@ import ( "fmt" ) -type PolicyType int - -type PolicyMap map[PolicyType]string - -const ( - NetworkFireWallPolicy PolicyType = iota - NetworkPriorityPolicy - NetworkBandwidthPolicy - NetworkLoadbalancePolicy - StorageRatelimitPolicy - StorageSnapshotPolicy -) - -var AllowedPolicyTypes = map[PolicyType]string{ - NetworkFireWallPolicy: "network-firewall-policy", - NetworkPriorityPolicy: "network-priority-policy", - NetworkBandwidthPolicy: "network-bandwidth-policy", - NetworkLoadbalancePolicy: "network-loadbalance-policy", - StorageRatelimitPolicy: "storage-ratelimit-policy", - StorageSnapshotPolicy: "storage-snapshot-policy", -} - -var AllowedPolicyStrings = map[string]PolicyType{ - "network-firewall-policy": NetworkFireWallPolicy, - "network-priority-policy": NetworkPriorityPolicy, - "network-bandwidth-policy": NetworkBandwidthPolicy, - "network-loadbalance-policy": NetworkLoadbalancePolicy, - "storage-ratelimit-policy": StorageRatelimitPolicy, - "storage-snapshot-policy": StorageSnapshotPolicy, +// PolicyAttribute defines the value of granular policy construct identified +// by Type. Network bandwidth, quality of service are a few examples of network +// policy attributes +type PolicyAttribute interface { + json.Marshaler + json.Unmarshaler + Type() string } -func (pt PolicyType) IsValidType() bool { - _, ok := AllowedPolicyTypes[pt] - return ok +// Policy is a collection policy attributes associated with a category like +// network or storage. The CategoryInstance is the identifier for a single instance +// of the Category for the which policy attributes are meant. For instace, for a +// network policy this will be the name of the network to which the policy applies. +type Policy struct { + Category string `json:"category"` + CategoryInstance string `json:"category_instance"` + Attributes []PolicyAttribute `json:"attributes"` } -func (pt PolicyType) String() string { - str, ok := AllowedPolicyTypes[pt] - if !ok { - return "undefined" - } - return str -} - -func (pt PolicyType) MarshalJSON() ([]byte, error) { - if !pt.IsValidType() { - return nil, fmt.Errorf("unsupported policy type: %d", pt) - } - return json.Marshal(pt.String()) -} +func (p *Policy) UnmarshalJSON(in []byte) error { + val := struct { + Category string `json:"category"` + CategoryInstance string `json:"category_instance"` + Attributes []struct { + Type string `json:"type"` + Data interface{} `json:"data"` + } `json:"attributes"` + }{} -func (pt PolicyType) UnmarshalJSON(in []byte) error { - v, ok := AllowedPolicyStrings[string(in)] - if !ok { - return fmt.Errorf("unsupported policy string: %q", in) + if err := json.Unmarshal(in, &val); err != nil { + return err } - pt = v - return nil -} -func (pm PolicyMap) MarshalJSON() ([]byte, error) { - m := make(map[string]string) - for k, v := range pm { - if !k.IsValidType() { - return nil, fmt.Errorf("unsupported policy type: %d", k) + p.Category = val.Category + p.CategoryInstance = val.CategoryInstance + var attr PolicyAttribute + for _, a := range val.Attributes { + switch a.Type { + case NetworkFirewallAttrType: + attr = NewNetworkFirewallAttr() + case NetworkBandwidthAttrType: + attr = NewNetworkBandwidthAttr("") + case NetworkCOSAttrType: + attr = NewNetworkCOSAttr(0) + case NetworkVendorSpecificAttrType: + attr = NewNetworkVendorSpecificAttr("") + default: + return fmt.Errorf("invalid policy attribute type %q", a.Type) } - m[k.String()] = v - } - return json.Marshal(m) -} - -func (pm PolicyMap) UnmarshalJSON(in []byte) error { - m := make(map[string]string) - if err := json.Unmarshal(in, &m); err != nil { - return err - } - for k, v := range m { - pt, ok := AllowedPolicyStrings[k] - if !ok { - return fmt.Errorf("unsupported policy string: %q", in) + aBytes, err := json.Marshal(a) + if err != nil { + return err + } + if err := json.Unmarshal(aBytes, &attr); err != nil { + return err } - pm[pt] = v + p.Attributes = append(p.Attributes, attr) } return nil } diff --git a/vendor/src/github.com/docker/engine-api/types/container/policy_test.go b/vendor/src/github.com/docker/engine-api/types/container/policy_test.go new file mode 100644 index 0000000000000..f4650a0111e90 --- /dev/null +++ b/vendor/src/github.com/docker/engine-api/types/container/policy_test.go @@ -0,0 +1,166 @@ +package container + +import ( + "encoding/json" + "sort" + "testing" + + . "github.com/go-check/check" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { TestingT(t) } + +type PolicyTestSuite struct { +} + +var _ = Suite(&PolicyTestSuite{}) + +type sortedAttrs []PolicyAttribute + +func (sa sortedAttrs) Len() int { + return len(sa) +} + +func (sa sortedAttrs) Less(i, j int) bool { + return sa[i].Type() < sa[j].Type() +} + +func (sa sortedAttrs) Swap(i, j int) { + sa[i], sa[j] = sa[j], sa[i] +} + +type PoliciesSlc []Policy + +func (s *PolicyTestSuite) TestPolicyMarshal(c *C) { + p := struct { + Policies []Policy `json:"Policies"` + }{ + Policies: []Policy{ + { + Category: "foo", + CategoryInstance: "bar", + Attributes: []PolicyAttribute{ + NewNetworkCOSAttr(1), + NewNetworkBandwidthAttr("bw100"), + NewNetworkFirewallAttr(), + NewNetworkVendorSpecificAttr("vendor1"), + }, + }, + }, + } + + exptdOut := `{ + "Policies" :[{ + "category": "foo", + "category_instance": "bar", + "attributes": [ + { + "type": "vendor-specific", + "data": "vendor1" + }, + { + "type": "cos", + "data": 1 + }, + { + "type": "bandwidth", + "data": "bw100" + }, + { + "type": "firewall", + "data": { + "rules": [], + "groupId": "" + } + } + ] + }]}` + + out, err := json.Marshal(p) + c.Assert(err, IsNil) + var ( + oJSON map[string]interface{} + eJSON map[string]interface{} + ) + c.Assert(json.Unmarshal(out, &oJSON), IsNil) + c.Assert(json.Unmarshal([]byte(exptdOut), &eJSON), IsNil) + //DeepEquals will not work as is because the attributes in the slice + // are not in a particular order, so we resort to a more manual comparison + c.Assert(len(oJSON), Equals, len(eJSON)) + c.Assert(oJSON["Policies"], FitsTypeOf, eJSON["Policies"]) + oPolicy := oJSON["Policies"].([]interface{})[0].(map[string]interface{}) + ePolicy := eJSON["Policies"].([]interface{})[0].(map[string]interface{}) + c.Assert(oPolicy["category"], Equals, ePolicy["category"]) + c.Assert(oPolicy["category_instance"], Equals, ePolicy["category_instance"]) + c.Assert(oPolicy["attributes"], FitsTypeOf, ePolicy["attributes"]) + oAttrs := oPolicy["attributes"].([]interface{}) + eAttrs := ePolicy["attributes"].([]interface{}) + c.Assert(len(oAttrs), Equals, len(eAttrs)) + for i := range oAttrs { + matches := false + for j := range eAttrs { + + if matches, _ = DeepEquals.Check([]interface{}{oAttrs[i], eAttrs[j]}, []string{}); matches { + break + } + } + c.Assert(matches, Equals, true, + Commentf("output attr: %+v not found in expected attrs %+v", oAttrs[i], eAttrs)) + } +} + +func (s *PolicyTestSuite) TestPolicyUnmarshal(c *C) { + policies := `[{ + "category": "foo", + "category_instance": "bar", + "attributes": [ + { + "type": "vendor-specific", + "data": "vendor1" + }, + { + "type": "cos", + "data": 1 + }, + { + "type": "bandwidth", + "data": "bw100" + }, + { + "type": "firewall", + "data": { + "rules": [], + "groupId": "" + } + } + ] + }]` + + ePolicy := &Policy{ + Category: "foo", + CategoryInstance: "bar", + Attributes: []PolicyAttribute{ + NewNetworkCOSAttr(1), + NewNetworkBandwidthAttr("bw100"), + NewNetworkFirewallAttr(), + NewNetworkVendorSpecificAttr("vendor1"), + }, + } + + oPolicies := []*Policy{} + c.Assert(json.Unmarshal([]byte(policies), &oPolicies), IsNil) + //DeepEquals will not work as is because the attributes in the slice + // are not in a particular order, so we resort to a more manual comparison + oPolicy := oPolicies[0] + c.Assert(oPolicy.Category, Equals, ePolicy.Category) + c.Assert(oPolicy.CategoryInstance, Equals, ePolicy.CategoryInstance) + oAttrsSorted := sortedAttrs(oPolicy.Attributes) + sort.Sort(oAttrsSorted) + eAttrsSorted := sortedAttrs(ePolicy.Attributes) + sort.Sort(eAttrsSorted) + c.Assert(len(oAttrsSorted), Equals, len(eAttrsSorted)) + for i := range oAttrsSorted { + c.Assert(oAttrsSorted[i], DeepEquals, eAttrsSorted[i]) + } +}