Skip to content
This repository was archived by the owner on Jul 26, 2019. It is now read-only.

Comments

API Entitlement#61

Merged
n4ss merged 28 commits intomoby:masterfrom
ashfall:api-entitlement-2
Dec 21, 2017
Merged

API Entitlement#61
n4ss merged 28 commits intomoby:masterfrom
ashfall:api-entitlement-2

Conversation

@ashfall
Copy link
Contributor

@ashfall ashfall commented Oct 25, 2017

API entitlement allows a container management software to store at the entitlement-level the APIs and API subsets that the container is allowed [or not] to access.

The API entitlement's id api.access and takes a string argument of the following format: api-identifier:api-subset:access-rule (whole entitlement string should be api.access=id:subset:access;

  • api-identifier is the API identifier that the container management software wants to manage access to
  • api-subset allows the container management software to define if it wants to regulate access to the whole API (through the supported keyword all or only a subset, ex:engine:swarm or k8s:pod-create)
  • access-rule must be either deny or allow

We provide a default setup in Moby for Swarm, we should provide one for kub too.

Note:
The API entitlement is the first entitlement taking a string as a parameter so rework has been done on this end.

Copy link
Contributor Author

@ashfall ashfall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@n4ss Pushed a WIP version -- had some questions below, which we couldn't discuss when pairing.


const (
Allow APIAccess = "allow"
Access APIAccess = "access"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need APIAccess="Access"?

Copy link
Contributor

@n4ss n4ss Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I only remember us deciding on:

Access APIAccess = "allow"
Deny APIAccess = "deny"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why adding Access APIAccess = "access" ?

Also for the type, it's better to have a different type when it is enumerable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is a typo from our pairing session. I'll keep Allow and Deny, and remove the third line

Copy link
Contributor

@n4ss n4ss Nov 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n4ss@671f14b#diff-68edf18e069363d58649944c4d3ec6fbR17

Indeed, my bad!

LGTM for Allow/Deny! ^_^

apiEnt := DefaultEntitlements[entitlementID]

_, err := apiEnt.Enforce(ociProfile)
require.Equal(t, fmt.Errorf("OCI profile's APIAccess field nil"), err)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do I set up the ociprofile so its APIAccess field is not nil?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't finish this part last time, it was in the todo list iirc. NewOCIProfile() would need an additional parameter.

return &OCIProfile{
OCI: ociSpec,
AppArmorSetup: nil,
APIAccess: &APIAccessConfig{make(map[APIID]map[APISubsetId]APIAccess)},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the parsed APIAccess string needs to be passed to NewOCIProfile function

@ashfall ashfall requested a review from n4ss October 25, 2017 21:26
apiEnt := DefaultEntitlements[entitlementID]

_, err := apiEnt.Enforce(ociProfile)
require.Equal(t, fmt.Errorf("OCI profile's APIAccess field nil"), err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't finish this part last time, it was in the todo list iirc. NewOCIProfile() would need an additional parameter.

engineDomain = "engine"

// FIXME: we need to define the possible domain, identifier and value for the entitlement.
APIEntID = engineDomain + ".api"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since api would be the entitlement's domain and engine the subdomain and swarm the ID, it'd make more sense to have api at the beginning I think and the following format:
docker run --entitlements="api:engine.swarm=all:deny"

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!


apiSubsetAndAccessFields := strings.Split(apiSubsetAndAccess, ":")
if len(apiSubsetAndAccessFields) != 3 {
return nil, fmt.Errorf("Wrong API subset and access format, should be \"api-id:subset:[allow|deny]\"")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up from previous comment
\"api-id:subset:[allow|deny]\" -> \"api-id=subset:[allow|deny]\"

}

apiSubsetAndAccessFields := strings.Split(apiSubsetAndAccess, ":")
if len(apiSubsetAndAccessFields) != 3 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also split by = as : won't return every field.

_, err := apiEnt.Enforce(ociProfile)
require.Equal(t, fmt.Errorf("OCI profile's APIAccess field nil"), err)

// require.NoError(t, err, "Failed to enforce while testing entitlement %s", entitlementID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably remove all these comments as we won't test the OCI specs content in this entitlement

// FIXME: we need to define the possible domain, identifier and value for the entitlement.
APIEntID = engineDomain + ".api"
// e.g. defining an ent like so: "engine.api=swarm:all:allow"
APIEntAllowID = APIEntID + "=swarm:all:allow"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APIEntAccessID ? (access can be either allow or deny, people might think this only whitelists)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, at present, the value (allow/deny) is processed by NewStringEntitlement (through parser.ParseStringEntitlement). this means ha we'll need to have two different API IDs for alllow and deny, so the access specified is no lost in translation.

Since this is the first Entitlement to have a value, we might be able to account for the value processing elsewhere perhaps?

HostProcessesNoneEntFullID: entitlement.Entitlement(hostProcessesNoneEntitlement),
HostProcessesAdminEntFullID: entitlement.Entitlement(hostProcessesAdminEntitlement),

APIEntAllowID: entitlement.Entitlement(apiEntitlement),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as before on the APIEntAccessID

}

// NewOCIProfile instantiates an OCIProfile object with an OCI specification structure
func NewOCIProfile(ociSpec *specs.Spec, apparmorProfileName string) *OCIProfile {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably have one more parameter to specify APIAccess ^_^

Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
@codecov-io
Copy link

codecov-io commented Nov 1, 2017

Codecov Report

Merging #61 into master will increase coverage by 3.8%.
The diff coverage is 91.34%.

@@           Coverage Diff            @@
##           master     #61     +/-   ##
========================================
+ Coverage    64.7%   68.5%   +3.8%     
========================================
  Files          16      18      +2     
  Lines        1136     962    -174     
========================================
- Hits          735     659     -76     
+ Misses        344     247     -97     
+ Partials       57      56      -1

@GordonTheTurtle
Copy link

Please sign your commits following these rules:
https://github.com/moby/moby/blob/master/CONTRIBUTING.md#sign-your-work
The easiest way to do this is to amend the last commit:

$ git clone -b "api-entitlement-2" git@github.com:ashfall/libentitlement.git somewhere
$ cd somewhere
$ git rebase -i HEAD~842354312520
editor opens
change each 'pick' to 'edit'
save the file and quit
$ git commit --amend -s --no-edit
$ git rebase --continue # and repeat the amend for each commit
$ git push -f

Amending updates the existing PR. You DO NOT need to open a new one.

n4ss and others added 17 commits November 3, 2017 16:11
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
@ashfall ashfall changed the title WIP: API Entitlements API Entitlements Nov 10, 2017
const (
// APIEntFullID is the API entitlement identifier; the value format is: "api.access:api-id:subset:[allow|deny]"
// ex: "api.access:engine.swarm:all:allow"
APIEntFullID = "api.access"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could only use 'api', this means that we have to update the DNS convention of accepting an empty domain for entitlements. Plenty of stuff to rewrite in that case.

We should open an issue for this but it's probably low priority.

}

if ociProfile.APIAccessConfig == nil {
return false, secprofile.Allow, fmt.Errorf("OCI profile's APIAccess field nil")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment for all errors, we should probably have all these in an error package so that we can reuse them easily in code + test. (less error-prone)


apiSubset, ok := ociProfile.APIAccessConfig.APIRights[swarmAPIFullID]
if !ok || apiSubset == nil {
return false, secprofile.Allow, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secprofile.Allow should probably be the result of a GetDefaultAccess() function accessible outside of this package.

access := secprofile.APIAccess(accessStr)

switch access {
case secprofile.Deny, secprofile.Allow:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We can also provide an isValidAccessString(string) to do that if we come to find other spots where we need it. low priority

@n4ss
Copy link
Contributor

n4ss commented Nov 10, 2017

can you take a look plz @riyazdf?

// Default known APIs and API subsets to control access of
const (
// EngineAPI defines the Moby-Engine API
EngineAPI = "engine"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to enforce an API version, as both of these APIs may add new endpoints or introduce breaking changes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, let's create a set-able version variable so that Moby can tune it. Do you think we should accept different rules for different versions of the API?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a valid scenario for running multiple versions of the API simultaneously? I can't think of one, so I think it should just be a single version at a time.


/*IsSwarmAPIControlled checks if Moby Swarm API is controlled and whether it's allowed or not
* Return values are the following:
* isControlled - if no error is encountered, whether the Swarm API is currently controlled by the entitlements
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this just be captured in access? If isControlled is false, we can just return access == secprofile.Allow

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I thought IsSwarmAPIControlled would make more sense as the default behavior should be controlled by the container management platform.

Follow-up note: we should have a set-able DefaultAccess that'd be returned in case of an error.

parser/parser.go Outdated

import (
"fmt"
"github.com/Sirupsen/logrus"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking nit: let's use a separate import block for non-standard packages

parser/parser.go Outdated

idAndArgList := strings.Split(idAndArgString, "=")
if len(idAndArgList) != 2 {
id = idAndArgList[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking nit: this assignment should remain after the if len(idAndArgList) > 2 { check

Copy link
Contributor

@n4ss n4ss Nov 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/me shame
Good catch!

require.NoError(t, err)

_, err = apiEnt.Enforce(ociProfile)
require.Equal(t, err, fmt.Errorf("Wrong API subset and access format, should be \"api-id:subset:[allow|deny]\""))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we plan to add more errors, it'd probably be better to make a test-slice/map for a cleaner function

}

apiIDStr := apiToAccessFields[0]
apiSubsetStr := apiToAccessFields[1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the subset string the raw API path or a different mapping? And if so to the latter, how is it specified?

And do we plan to limit certain arguments? For example, --privileged since it's settable with container/create?

// Default known APIs and API subsets to control access of
const (
// EngineAPI defines the Moby-Engine API
EngineAPI = "engine"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of engine and swarm, should this instead be image/container/volume/swarm/etc as specified in the Docker API docs? https://docs.docker.com/engine/api/v1.33 I'm not sure I understand the distinction here

n4ss added 8 commits December 21, 2017 15:25
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
@n4ss n4ss merged commit 80ec3b1 into moby:master Dec 21, 2017
@n4ss n4ss changed the title API Entitlements API Entitlement Dec 21, 2017
@justincormack
Copy link

How is this expected to be implemented?

@justincormack
Copy link

justincormack commented Jan 2, 2018

Originally I was thinking that this would be just a control for bind mounting the socket, but obviously that can't do access control unless there is an authz plugin configured. But in that case the container needs a user identity, so we would then need container identity, and a user cert for this use case. But then it starts to look like generic API access control, not a specific entitlement, and is not much different from another remote API.

See also discussion in moby/moby#35711 about TLS for unix socket

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants