docker-registry icon indicating copy to clipboard operation
docker-registry copied to clipboard

How to disable users to perform push operation in a private docker-registry with basic authentication

Open joda70 opened this issue 11 years ago • 22 comments

I have configured in a given machine a private docker registry on CentOS 6.6 with basic authentication by using docker-registry 0.8.1-2 docker-io 1.2.0-3 nginx 1.6.2-1

I have included authorized users in the docker-registry.htpasswd.

Until now I have not found yet a way that allows me to disable part of the authorized users to perform docker push operation. Basically I would like to allow some users to only perform pull operation, whilst others also push.

Do you think it is possible such configuration without using docker hub?

joda70 avatar Nov 18 '14 17:11 joda70

I assume you can do that by restricting access to the PUT http method - but this is untested territories.

dmp42 avatar Nov 18 '14 17:11 dmp42

I can test it for you. Do you have any idea how to do the configuration? Or can you provide me with useful documentation? Basically only some users can run 'docker push ...' with success. All the others can just run pull.

joda70 avatar Nov 18 '14 17:11 joda70

I never went down that road myself, and I don't believe there is specific docs about it - but configuring nginx shouldn't be too hard.

dmp42 avatar Nov 18 '14 18:11 dmp42

+1

larrycai avatar Nov 25 '14 08:11 larrycai

There is probably a nicer way to do that with nginx, but I have been succesfully using this for HTTPS termination and PUT/POST/DELETE basic auth protection:

server {
  listen 80;
  server_name myregisty.net;

  location / {
    rewrite ^(.*)$ https://myregisty.net$1 last;
  }
}

server {
  listen 443;
  server_name myregisty.net;

  error_page 588 = @readrequests;

  ssl                       on;
  ssl_certificate           /etc/nginx/ssl/wildcard.myregisty.net.pem;
  ssl_certificate_key       /etc/nginx/ssl/wildcard.myregisty.net.pem;
  ssl_dhparam               /etc/nginx/ssl/wildcard.myregisty.net.pem;

  if ($request_method = 'GET') {
    return 588;
  }

  if ($request_method = 'HEAD') {
    return 588;
  }

  if ($request_method = 'OPTIONS') {
    return 588;
  }

  location / {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    client_max_body_size 800M; # avoid HTTP 413 for large image uploads
    chunked_transfer_encoding on; # required to avoid HTTP 411: see Issue #1486 (https://github.com/dotcloud/docker/issues/1486)
    proxy_pass http://127.0.0.1:5000;
  }

  location @readrequests {
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    client_max_body_size 800M; # avoid HTTP 413 for large image uploads
    chunked_transfer_encoding on; # required to avoid HTTP 411: see Issue #1486 (https://github.com/dotcloud/docker/issues/1486)
    proxy_pass http://127.0.0.1:5000;
  }
}

Users can be added via htpasswd -c /etc/nginx/.htpasswd newuser

bjaglin avatar Nov 29 '14 14:11 bjaglin

I am going to try what you suggested. Let you know as soon as possible.

However, do you think it is possible to support anonymous access to get images (so for pulling images) and register users that have to perform the push operation?

joda70 avatar Nov 29 '14 14:11 joda70

That's exactly what the snippet above achieves: no auth for pulls, but auth required (via docker login which just creates a ~/.dockercfg) for push/deletes.

bjaglin avatar Nov 29 '14 15:11 bjaglin

Thanks.

joda70 avatar Nov 29 '14 16:11 joda70

@bjaglin I tried it and works perfect for me on pull/push/login commands !!! this shall be default configuration in nginx-registry, recommend to put under https://github.com/docker/docker-registry/tree/master/contrib/nginx

One extra questions for messages on unauthorized request (push without login)

docker@boot2docker:~$ docker push dokk.co/hello-world
The push refers to a repository [dokk.co/hello-world] (len: 1)
Sending image list

It doesn't clearly indicate the http 401 error, which I can see in nginx log, like

172.17.42.1 - - [01/Dec/2014:04:27:59 +0000] "PUT /v1/repositories/hello-world/ HTTP/1.1" 401 194 "-" "docker/1.3.2 go/go1.3.3 git-commit/39fa2fa kernel/3.16.7-tinycore64 os/linux arch/amd64"

or docker.log like

[debug] http.go:162 https://dokk.co/v1/repositories/hello-world/ -- HEADERS: map[User-Agent:    
[docker/1.3.2 go/go1.3.3 git-commit/39fa2fa kernel/3.16.7-tinycore64 os/linux arch/amd64]     
Authorization:[Basic Og==]]
Error: Status 401 trying to push repository hello-world: <html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.6.2</center>
</body>
</html>

[998cd712] -job push(dokk.co/hello-world) = ERR (1)

Can we add extra configuration in nginx to response better warning

larrycai avatar Dec 01 '14 05:12 larrycai

@bjaglin I have tried your solution. I confirm the same behaviour obtained by @larrycai. Where should I introduce the http check to obtain a clear error in case of unauthorized request?

joda70 avatar Dec 01 '14 08:12 joda70

@bjaglin for line

 rewrite ^(.*)$ https://myregisty.net$1 last;

could be better for below ?

 rewrite ^(.*)$ https://$host$1 last;

so if I test it locally, curl http://localhost it can redirect to https://localhost (I run it under nginx docker environment)

larrycai avatar Dec 01 '14 10:12 larrycai

Have you found a way to return HTTP 401 error for users unregistered?

joda70 avatar Dec 03 '14 17:12 joda70

Hi,

I have tested a slightly shorter nginx configuration, which seems to allow pull for everyone but restrict push for authenticated users.

registry.conf:

server {
  ...

  location /v1/_ping {
    auth_basic off;
    include registry.proxy;
  }

  location /v1 {
    include registry.auth;
    include registry.proxy;
  }
}

registry.proxy:

proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host              $http_host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header Authorization     "";

proxy_pass http://127.0.0.1:5000;

registry.auth:

limit_except GET HEAD {
  auth_basic "Docker Registry";
  auth_basic_user_file registry.passwd;
}

I have split registry.auth in a different file so that I can change its content when I run the docker-registry container. The nginx startup script I use selects an appropriate registry.auth file depending on the value of an environment variable, so I can have one that completely disable authentication, for exemple.

ghost avatar Dec 15 '14 19:12 ghost

@larrycai @eolamey thanks for the tips! My config is now down to:

server {
  listen 80;
  server_name myregisty.net;

  location / {
    rewrite ^(.*)$ https://$host$1 last;
  }
}

server {
  listen 443;
  server_name myregisty.net;

  ssl                       on;
  ssl_certificate           /etc/nginx/ssl/wildcard.myregisty.net.pem;
  ssl_certificate_key       /etc/nginx/ssl/wildcard.myregisty.net.pem;
  ssl_dhparam               /etc/nginx/ssl/wildcard.myregisty.net.pem;

  location / {
    limit_except GET HEAD OPTIONS {
      auth_basic "Restricted";
      auth_basic_user_file /etc/nginx/.htpasswd;
    }
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    client_max_body_size 800M; # avoid HTTP 413 for large image uploads
    chunked_transfer_encoding on; # required to avoid HTTP 411: see Issue #1486 (https://github.com/dotcloud/docker/issues/1486)
    proxy_pass http://127.0.0.1:5000;
  }

}

bjaglin avatar Dec 15 '14 21:12 bjaglin

@bjaglin did you solve the problem I mentioned above ? cc @joda70

not clear message in docker push without authentication, though it shows 401 error in daemon logs.

It looks there are code in docker daemon to handle this logic, don't know why it doesn't work. https://github.com/docker/docker/blob/master/api/client/commands.go (func CmdPush)

if err := push(authConfig); err != nil {
    if strings.Contains(err.Error(), "Status 401") {
        fmt.Fprintln(cli.out, "\nPlease login prior to push:")

larrycai avatar Dec 22 '14 02:12 larrycai

@bjaglin like the new conf, quite clean, I also add simple LDAP certification as reference, see https://github.com/larrycai/nginx-registry , but above problem still there

larrycai avatar Jan 15 '15 09:01 larrycai

Hi

This is how we're deal with push using apache 2.2 + ldap (centos6) insted of nginx

<AuthnProviderAlias ldap ldap_confi>
        AuthLDAPURL 'ldap://xxx.yyy.zzz o=example,dc=com?uid?sub?(objectClass=*)'
</AuthnProviderAlias>

        <Location />
                AuthName "Registry Authentication"
                AuthType basic

                AuthBasicProvider ldap_config
                AuthzLDAPAuthoritative on

                <Limit PUT>
                        AuthLDAPURL "ldap://xxx.yyy.zzz o=example,dc=com?uid?sub?(objectClass=*)"
                        AuthLDAPGroupAttributeIsDN on
                        AuthLDAPGroupAttribute member
                        AuthLDAPRemoteUserAttribute uid
                        AuthLDAPRemoteUserIsDN on
                        Require ldap-group cn=admins,ou=metagroups,o=example,dc=com
                </Limit>

                <LimitExcept PUT>
                        Require valid-user
                </LimitExcept>

        </Location>
        <Location /v1/_ping>
                Satisfy any
                Allow from all
        </Location>

        <Location /_ping>
                Satisfy any
                Allow from all
        </Location>

Now I'm trying to add a new feature , a user can only push a img to it's own repository /v1/repositories/<USER>

ssalvatori avatar Mar 18 '15 12:03 ssalvatori

@ssalvatori Are you running the apache + ldap on a container? Why have you decided to use apache instead of Nginx?

joda70 avatar Apr 02 '15 07:04 joda70

Have you run docker-registry and nginx in two different containers?

joda70 avatar May 22 '15 17:05 joda70

Have you tried to change the nginx.conf configuration for the registry:2?

joda70 avatar Mar 03 '16 12:03 joda70

Hm, meanwhile it seems that if docker login requests GET /v2/ and doesn't get the request to authenticate it will not save the credentials. If you later on docker push to that site it will break on the HTTP 401s its getting from the post requests and will not try to authenticate then.

dothebart avatar Nov 16 '16 10:11 dothebart

@dothebart when using registry v2, nginx is useless.

you can use docker_auth image to realize this object, expecially more complex user ACLS. Please looking https://github.com/cesanta/docker_auth for more

wdq347 avatar Dec 22 '16 05:12 wdq347