greenled / portainer-stack-utils

CLI client for Portainer

Home Page:https://hub.docker.com/r/greenled/portainer-stack-utils/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Deploy stack with configs/secrets from file, manage configs/secrets

kaotika opened this issue · comments

Problem

At the moment you can't deploy a stack with non existing configs/secrets. Only external=true is possible.

Goal

  1. Similar behaviour like docker stack deploy -c stack.yml test --> deployment of a stack with configs/secrets imported from file.
configs:
  server-config:
    file: config.json
  1. actions to create a single config/secrets from file, like docker config create server-config ./config.json or docker secret create my_secret ./secret.json
  2. actions to show configs/secrets, like docker config ls or docker secret ls
  3. actions to delete a single configs/secrets

CLI

psu config create server-config ./config.json
psu secret create my_secret ./secret.json
psu config ls
psu secret ls
psu config delete server-config
psu secret delete my_secret

Possible Applications

  • replace docker cli for local deployments to make use of rbac
  • CI/CD deployment of stacks

Thank you @kaotika for putting all this together. I'll give it a shot later.

FTR, this thread started in #20.

@kaotika while adding config and secret management would in deed make the tool more powerful, it would also be a first step towards a "reinventing the wheel" situation. Soon it could get like "what about container management?", "and image management?", "and network management?", and ... after a while it would end up being a bad copy of the Docker client, and something practically impossible for me to maintain.

I have an idea that solves most parts of this issue. As the Portainer API exposes an endpoint's Docker API through /api/endpoint/{id}/docker, this tool could accept requests from a local Docker client and proxy them to the exposed Docker API. This would allow to run docker ... commads in a remote Docker daemon like in a local one (see #29).

What do you think?

@greenled this sounds like a great idea. I'am mostly happy with docker, except the absence of propper access control.

@kaotika please check out 2.0.0-alpha.3 release. It includes a first aproximation to proxying requests from a Docker client to Portainer, which should fulfill your config and secret management needs.

@greenled Wow, this is great! I did a quick check and basically it works. Great job! 🥇

What's not working, is creating a stack with configs. The proxy crashed with FATA. (the number always changes...is it important?, I ran the proxy with --log-level=debug)
I tried to track down the issue and find out, that creating a config or a secret works, but does not set the proper ownership to the logged in user. I assume, that prevents the stack deploy to work properly. Most likely this is a problem with portainer, rather psu.

@kaotika I found the root cause of the error. When a config/secret or any other Docker resource is created with the proxy method no resource control policy is added for it, so by default it belongs to administrators only. Creating a Docker resource in Portainer's web UI takes two calls: one to /api/endpoint/{id}/docker to create the resource and another one to /api/resource_controls to create a resource control for it. I'm working on a command for PSU to create/read/update/remove such resource controls.

I am considering two designs:

3 generic commands:

psu resource access set <--type container|volume|service|secret|config|stack|network> <--resource resourceId> <--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu resource access get <--type <container|volume|service|secret|config|stack|network>> <--resource <resourceId>>
psu resource access clear <--type <container|volume|service|secret|config|stack|network>> <--resource <resourceId>>

21 specific commands (3 for each of the 7 resource types, no --type flag, and resource id passed as an argument):

psu container access set <containerID> <--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu container access get <containerID>
psu container access clear <containerID>
psu volume access set <volumeID> <--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu volume access get <volumeID>
psu volume access clear <volumeID>
psu service access set <serviceID> <--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu service access get <serviceID>
psu service access clear <serviceID>
psu secret access set <secretID><--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu secret access get <secretID>
psu secret access clear <secretID>
psu config access set <configID><--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu config access get <configID>
psu config access clear <configID>
psu stack access set <stackID><--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu stack access get <stackID>
psu stack access clear <stackID>
psu network access set <networkID><--public | --teams <commaSeparatedTeamIds> | --users <commaSeparatedUserIds>> [--subresources <commaSeparatedSubresourceIds>]
psu network access get <networkID>
psu network access clear <networkID>

I would go with the second design as it's less error prone, focuses on the client user instead of the API, and follows the Docker client sintax without overlapping. Though, those 21 new commands look intimidating. Maybe if I find a way to generate them at runtime...

Which variant do you think would fit better? Or maybe you have another idea?

@deviantony please could you give some clarification here. I see Portainer injects a Portainer field with access control information in the JSON responses for inspecting Docker resources. This is widely used in the web UI, but it's not documented anywhere I could find. Is this part of the Portainer public API? Is it ok if I build on top of this? I would use this feature in the implementation of the ... access get ... commands described above.

@greenled

Indeed, Portainer decorates all the Docker resources GET operation responses with a Portainer property if a resource control exists for the specified Docker resource.

This property is an object containing a ResourceControl object based on the definition available at: https://github.com/portainer/portainer/blob/develop/api/portainer.go#L441-L457

If no resource control is applied on the resource, this resource is considered to be manageable by Administrator users only.

I'm not sure why you would need to manage all the existing type of resource controls that we can set via Portainer as I thought this tool was only used to manage stacks. If that is the case, just manage resource controls set on stacks. When sending requests through the Portainer API, Portainer will take care of retrieving all the resources (and resource controls) associated to the stack.

So for example, if you set a restricted access on a stack, when you list containers Portainer will read the label associated to each container and if the stack label exists (com.docker.stack.namespace in Swarm stack for example) it will automatically decorate the resource with the stack resource control.

Thank you for the info @deviantony. The example you provide works for resources created with the stack. The idea of managing resource control came from the need of using external configs and secrets in the deployed stacks (see #20), which can be generalized to using external: true resources when deploying stacks.

Portainer admin users can create and use resources by querying /endpoints/{Id}/docker, but in order for this to work for non-admin users they have to set proper resource control after creation. Any Docker resource can fall in the "external" category when deploying a stack, that's why I want to add resource control commands for each kind of resource.

BTW @kaotika, the number in the log after FATA represents how many seconds have passed since program started.

@greenled I think the design to just set the correct resources is the way to go. Everything else seems to complicated.

@kaotika on a second thought, design could be an even simpler (I think) mix of both aproaches. As removing an access control is the same as setting it to public, no clear commands. As I see little to no point in getting access control information in commandline (better use the web UI), no get commands. And the endpoint has to be specified, implicit or explicit.

psu stack access <stackName> <--admins | --private | --public> [--endpoint <endpointName>]
psu volume access <volumeName> <--admins | --private | --public> [--endpoint <endpointName>]
psu container access <containerName|containerID> <--admins | --private | --public> [--endpoint <endpointName>]
psu service access <serviceName|serviceID> <--admins | --private | --public> [--endpoint <endpointName>]
psu secret access <secretName|secretID> <--admins | --private | --public> [--endpoint <endpointName>]
psu config access <configName|configID> <--admins | --private | --public> [--endpoint <endpointName>]
psu network access <networkName|networkID> <--admins | --private | --public> [--endpoint <endpointName>]
  • --admins administrators only
  • --private current user only
  • --public all users

This new draft intentionally leaves users and groups lists out. They could be added later, as it involves a higher complexity.