Conversation
secprofile/oci_profile.go
Outdated
|
|
||
| const ( | ||
| Allow APIAccess = "allow" | ||
| Access APIAccess = "access" |
There was a problem hiding this comment.
Why do we need APIAccess="Access"?
There was a problem hiding this comment.
hmm I only remember us deciding on:
Access APIAccess = "allow"
Deny APIAccess = "deny"There was a problem hiding this comment.
Why adding Access APIAccess = "access" ?
Also for the type, it's better to have a different type when it is enumerable.
There was a problem hiding this comment.
I believe this is a typo from our pairing session. I'll keep Allow and Deny, and remove the third line
There was a problem hiding this comment.
defaults/api_ents_test.go
Outdated
| apiEnt := DefaultEntitlements[entitlementID] | ||
|
|
||
| _, err := apiEnt.Enforce(ociProfile) | ||
| require.Equal(t, fmt.Errorf("OCI profile's APIAccess field nil"), err) |
There was a problem hiding this comment.
How do I set up the ociprofile so its APIAccess field is not nil?
There was a problem hiding this comment.
We didn't finish this part last time, it was in the todo list iirc. NewOCIProfile() would need an additional parameter.
secprofile/oci_profile.go
Outdated
| return &OCIProfile{ | ||
| OCI: ociSpec, | ||
| AppArmorSetup: nil, | ||
| APIAccess: &APIAccessConfig{make(map[APIID]map[APISubsetId]APIAccess)}, |
There was a problem hiding this comment.
I think the parsed APIAccess string needs to be passed to NewOCIProfile function
defaults/api_ents_test.go
Outdated
| apiEnt := DefaultEntitlements[entitlementID] | ||
|
|
||
| _, err := apiEnt.Enforce(ociProfile) | ||
| require.Equal(t, fmt.Errorf("OCI profile's APIAccess field nil"), err) |
There was a problem hiding this comment.
We didn't finish this part last time, it was in the todo list iirc. NewOCIProfile() would need an additional parameter.
defaults/api_ents.go
Outdated
| engineDomain = "engine" | ||
|
|
||
| // FIXME: we need to define the possible domain, identifier and value for the entitlement. | ||
| APIEntID = engineDomain + ".api" |
There was a problem hiding this comment.
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?
defaults/api_ents.go
Outdated
|
|
||
| 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]\"") |
There was a problem hiding this comment.
Follow-up from previous comment
\"api-id:subset:[allow|deny]\" -> \"api-id=subset:[allow|deny]\"
defaults/api_ents.go
Outdated
| } | ||
|
|
||
| apiSubsetAndAccessFields := strings.Split(apiSubsetAndAccess, ":") | ||
| if len(apiSubsetAndAccessFields) != 3 { |
There was a problem hiding this comment.
We should also split by = as : won't return every field.
defaults/api_ents_test.go
Outdated
| _, 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) |
There was a problem hiding this comment.
You can probably remove all these comments as we won't test the OCI specs content in this entitlement
defaults/api_ents.go
Outdated
| // 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" |
There was a problem hiding this comment.
APIEntAccessID ? (access can be either allow or deny, people might think this only whitelists)
There was a problem hiding this comment.
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?
defaults/defaults.go
Outdated
| HostProcessesNoneEntFullID: entitlement.Entitlement(hostProcessesNoneEntitlement), | ||
| HostProcessesAdminEntFullID: entitlement.Entitlement(hostProcessesAdminEntitlement), | ||
|
|
||
| APIEntAllowID: entitlement.Entitlement(apiEntitlement), |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
We should probably have one more parameter to specify APIAccess ^_^
34a268c to
d275458
Compare
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
Signed-off-by: Ashwini Oruganti <ashwini.oruganti@gmail.com> (github: ashfall)
d275458 to
4ea5b4f
Compare
Codecov Report
@@ 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 |
|
Please sign your commits following these rules: $ 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 -fAmending updates the existing PR. You DO NOT need to open a new one. |
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)
| 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" |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
nit: We can also provide an isValidAccessString(string) to do that if we come to find other spots where we need it. low priority
|
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" |
There was a problem hiding this comment.
We need to enforce an API version, as both of these APIs may add new endpoints or introduce breaking changes
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
should this just be captured in access? If isControlled is false, we can just return access == secprofile.Allow
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
non-blocking nit: this assignment should remain after the if len(idAndArgList) > 2 { check
| 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]\"")) |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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
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>
|
How is this expected to be implemented? |
|
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 |
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.accessand takes a string argument of the following format:api-identifier:api-subset:access-rule(whole entitlement string should beapi.access=id:subset:access;api-identifieris the API identifier that the container management software wants to manage access toapi-subsetallows the container management software to define if it wants to regulate access to the whole API (through the supported keywordallor only a subset, ex:engine:swarmork8s:pod-create)access-rulemust be eitherdenyorallowWe 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.