Feature #1875

A REST Authorization file is needed

Added by Luke Kanies over 1 year ago. Updated over 1 year ago.

Status:Closed Start:01/19/2009
Priority:High Due date:
Assignee:Brice Figureau % Done:

0%

Category:network
Target version:0.25.0
Affected version:0.24.7 Branch:
Keywords:
Votes: 0

Description

In the switch from XMLRPC to REST, we’ve exposed the lack of decent authorization in the network interfaces.

Note that authorization is distinct from authentication, which is handled via certificates.

Once a host is authenticated, we need some way of knowing what services or aspects of services the host has access to. Current 0.24.x systems provide multiple configuration files that cover some of this, notably fileserver.conf. For arbitrary authorization, there is namespaceauth.conf.

We need something more generic that relies on our switch to REST, likely providing authorization against URIs, possibly with some kind of mapping to services.

E.g., we might have something like:

  path /
  deny *

  path /file
  allow authenticated

  path /certificate
  allow unauthenticated

  path /file/$host
  deny *
  allow $host # or something like that

Clearly I haven’t exactly thought this all the way through.

The main thing we need is something mirrors our existing default authentication – defer to fileserver.conf for files, allow unauthenticated users to upload certificate requests and download certificates, and allow authenticated users to download catalogs.

It’d be nice to be able to fold fileserver.conf into this file eventually, I think, but I wouldn’t prioritize that.

Note that there are three fields to any authentication attempt: path/uri, authentication status, http verb. Any configuration file would need to be able to specify each of these in a given authorization specification.

It’d be best of all if we could find someone else’s solution to this problem and crib from them.


Related issues

related to Puppet - Feature #2178: Strict hostname checking should require matching the IP r... Accepted 04/21/2009

History

Updated by Luke Kanies over 1 year ago

  • Assignee changed from Paul Nasrat to Brice Figureau

Brice has actually taken this over.

Updated by Luke Kanies over 1 year ago

  • Status changed from Accepted to Ready for Testing

Sounds like this is almost ready, just a couple of small changes left.

Updated by Luke Kanies over 1 year ago

In testing this, I think that it makes more sense to have something like a default configuration for authenticated connections, and then a different default configuration for unauthenticated connections.

From what I can tell of the current configuration, any unauthenticated client can save certificates to the server, which I think is a pretty significant security hole. The only things that should be possible for unauth’d clients is to save CSRs and retrieve certificates.

Updated by Brice Figureau over 1 year ago

luke wrote:

In testing this, I think that it makes more sense to have something like a default configuration for authenticated connections, and then a different default configuration for unauthenticated connections.

From what I can tell of the current configuration, any unauthenticated client can save certificates to the server, which I think is a pretty significant security hole. The only things that should be possible for unauth’d clients is to save CSRs and retrieve certificates.

So your suggestion would be:

to be able to restrict an ACL to an authenticated or unauthenticated request in the auth.conf file

to let the unauthenticated case use the same code as the authenticated case

to have the default set of rule define rules for authenticated and unauthenticated cases

Is that right?

Updated by Brice Figureau over 1 year ago

luke wrote:

In testing this, I think that it makes more sense to have something like a default configuration for authenticated connections, and then a different default configuration for unauthenticated connections.

From what I can tell of the current configuration, any unauthenticated client can save certificates to the server, which I think is a pretty significant security hole. The only things that should be possible for unauth’d clients is to save CSRs and retrieve certificates.

I have an implementation (and posted it for review to the puppet-dev list) in my tickets/1875 github branch: http://github.com/masterzen/puppet/tree/tickets/1875

Updated by Brice Figureau over 1 year ago

masterzen wrote:

luke wrote:

In testing this, I think that it makes more sense to have something like a default configuration for authenticated connections, and then a different default configuration for unauthenticated connections.

From what I can tell of the current configuration, any unauthenticated client can save certificates to the server, which I think is a pretty significant security hole. The only things that should be possible for unauth’d clients is to save CSRs and retrieve certificates.

I have an implementation (and posted it for review to the puppet-dev list) in my tickets/1875 github branch: http://github.com/masterzen/puppet/tree/tickets/1875

Latest (and hopefully) final implementation is in the tickets/master/1875 branch of my gh repository: http://github.com/masterzen/puppet/tree/tickets/master/1875

Note: it is rebased on top of lak’s tickets/master/2149, so to be applied after 2149.

Updated by Luke Kanies over 1 year ago

  • Status changed from Ready for Testing to Code Insufficient

I can’t seem to find an easy way to test this, but my client is apparently asking for the catalog for an unqualified host, and I’m getting an exception:

Users/luke/puppet/lib/puppet/network/authstore.rb:332:in `parse'
/Users/luke/puppet/lib/puppet/network/authstore.rb:193:in `pattern='
/Users/luke/puppet/lib/puppet/network/authstore.rb:228:in `interpolate'
/Users/luke/puppet/lib/puppet/network/authstore.rb:77:in `interpolate'
/Users/luke/puppet/lib/puppet/network/authstore.rb:76:in `collect'
/Users/luke/puppet/lib/puppet/network/authstore.rb:76:in `interpolate'
/Users/luke/puppet/lib/puppet/network/rights.rb:188:in `allowed?'
/Users/luke/puppet/lib/puppet/network/rights.rb:50:in `fail_on_deny'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/httputils.rb:236:in `find'
/Users/luke/puppet/lib/puppet/network/rights.rb:43:in `each'
/Users/luke/puppet/lib/puppet/network/rights.rb:43:in `find'
/Users/luke/puppet/lib/puppet/network/rights.rb:43:in `fail_on_deny'
/Users/luke/puppet/lib/puppet/network/rest_authconfig.rb:36:in `allowed?'
/Users/luke/puppet/lib/puppet/network/rest_authorization.rb:21:in `check_authorization'
/Users/luke/puppet/lib/puppet/network/http/handler.rb:46:in `process'
/Users/luke/puppet/lib/puppet/network/http/webrick/rest.rb:23:in `service'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:162:in `start'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:95:in `start'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:92:in `each'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:92:in `start'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:23:in `start'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/webrick/server.rb:82:in `start'
/Users/luke/puppet/lib/puppet/network/http/webrick.rb:40:in `listen'
/Users/luke/puppet/lib/puppet/network/http/webrick.rb:40:in `initialize'
/Users/luke/puppet/lib/puppet/network/http/webrick.rb:40:in `new'
/Users/luke/puppet/lib/puppet/network/http/webrick.rb:40:in `listen'
/Users/luke/puppet/lib/puppet/network/http/webrick.rb:37:in `synchronize'
/Users/luke/puppet/lib/puppet/network/http/webrick.rb:37:in `listen'
/Users/luke/puppet/lib/puppet/network/server.rb:128:in `listen'
/Users/luke/puppet/lib/puppet/network/server.rb:143:in `start'
/Users/luke/puppet/lib/puppet/daemon.rb:128:in `start'
/Users/luke/puppet/lib/puppet/application/puppetmasterd.rb:87:in `main'
/Users/luke/puppet/lib/puppet/application.rb:226:in `send'
/Users/luke/puppet/lib/puppet/application.rb:226:in `run_command'
/Users/luke/puppet/lib/puppet/application.rb:217:in `run'
sbin/puppetmasterd:66
err: Could not check authorization: Invalid pattern phage

Updated by Luke Kanies over 1 year ago

(Hit submit too soon.)

Note that I added an extra wrapper around the auth check to make this error read a bit better.

The only thing I’ve firmly established is that the problem occurs when my host (whose certname is other.madstop.com) asks for /catalog/phage (phage being its real name).

Updated by Brice Figureau over 1 year ago

luke wrote:

(Hit submit too soon.)

Note that I added an extra wrapper around the auth check to make this error read a bit better.

The only thing I’ve firmly established is that the problem occurs when my host (whose certname is other.madstop.com) asks for /catalog/phage (phage being its real name).

That’s why I asked you a couple of days ago on #puppet why you used Facter.value(“hostname”) for catalog retrieval, and Puppet[:certname] for facts retrieval, because I was hitting the same issue. The real cause of the problem is that the underlying authstore requires fully qualified names, but the requests to catalog indirectors sends only the hostname, so in turns we’re trying to create a dynamic temporary allow(“hostname”), which is forbidden.

Now that I think about it, it looks like a bug, as hostnames can clearly not be unique among the various nodes connecting to a master (ie host1.domain1.com and host2.domain2.com would get the same request, and maybe the same catalog… after looking to the code, in fact no it works, because what matters is the request determined node which hopefully uses the certname).

So it’s just the catalog request that is wrong (the logic is OK), we should move from /catalog/hostname to /catalog/fqdn or /catalog/certname.

What do you think? If you’re OK, I’ll queue a patch to fix this issue.

Updated by Brice Figureau over 1 year ago

masterzen wrote:

luke wrote:

(Hit submit too soon.)

Note that I added an extra wrapper around the auth check to make this error read a bit better.

The only thing I’ve firmly established is that the problem occurs when my host (whose certname is other.madstop.com) asks for /catalog/phage (phage being its real name).

That’s why I asked you a couple of days ago on #puppet why you used Facter.value(“hostname”) for catalog retrieval, and Puppet[:certname] for facts retrieval, because I was hitting the same issue. The real cause of the problem is that the underlying authstore requires fully qualified names, but the requests to catalog indirectors sends only the hostname, so in turns we’re trying to create a dynamic temporary allow(“hostname”), which is forbidden.

Now that I think about it, it looks like a bug, as hostnames can clearly not be unique among the various nodes connecting to a master (ie host1.domain1.com and host2.domain2.com would get the same request, and maybe the same catalog… after looking to the code, in fact no it works, because what matters is the request determined node which hopefully uses the certname).

So it’s just the catalog request that is wrong (the logic is OK), we should move from /catalog/hostname to /catalog/fqdn or /catalog/certname.

What do you think? If you’re OK, I’ll queue a patch to fix this issue.

I updated my tickets/master/1875 branch with this fix included.

Updated by Luke Kanies over 1 year ago

  • Status changed from Code Insufficient to Ready for Checkin
  • Assignee changed from Brice Figureau to James Turnbull

It looks like this doesn’t allow blanket statements like ‘allow ’ or ‘deny ’.

I think that’s an existing limitation, though, so I’m comfortable merging as is and fixing that later.

I ran through a basic list of tests and everything seems to work, so I’m comfortable checking in as is.

Updated by James Turnbull over 1 year ago

  • Status changed from Ready for Checkin to Code Insufficient
  • Assignee changed from James Turnbull to Brice Figureau

Can you rebase tickets/master/1875 on current HEAD please.

Updated by Brice Figureau over 1 year ago

luke wrote:

It looks like this doesn’t allow blanket statements like ‘allow ’ or ‘deny ’.

I think that’s an existing limitation, though, so I’m comfortable merging as is and fixing that later.

‘allow *’ works fine. But deny * is not supported. This is because of the way authstore is working (ie it sorts allow rules before deny rules and finishes with an implit deny all).

So basically, it has no sense to write: allow something deny *

and a lone: deny * is equivalent to nothing.

If we want something which would allow more complex scenarios, we need something ala Apache, with a directive to specify in which order ACE should be evaluated: order allow, deny allow this deny * Then deny would be applied after the allow.

I thought about this, but in the end I think your system works, except we are tempted to add some “deny ” as catch-all rules, which is forbidden. And not that an “allow ” is treated before any other rules, so that even if you have some denies after that they can’t be reached.

If you think we need to change this, then let’s open a ticket, I’ll be happy to work on this.

I ran through a basic list of tests and everything seems to work, so I’m comfortable checking in as is.

Thanks, I’ll rebase to latest HEAD tonight.

Updated by Brice Figureau over 1 year ago

  • Status changed from Code Insufficient to Ready for Checkin

I rebased against the latest master, you can apply. Thanks,

Updated by James Turnbull over 1 year ago

  • Status changed from Ready for Checkin to Closed

Pushed in branch master.

Also available in: Atom PDF