On Apr 20, 2009, at 2:06 AM, Brice Figureau wrote:

>
> On Sun, 2009-04-19 at 22:24 -0500, Luke Kanies wrote:
>> Very nice.  A couple of comments below.
>>
>> On Apr 19, 2009, at 11:38 AM, Brice Figureau wrote:
>>
>>>
>>> Before this change, unauthenticated REST requests where
>>> inconditionnaly
>>> allowed, as long as they were to the certificate terminus.
>>> This could be a security hole, so now the REST requests,  
>>> authenticated
>>> or unauthenticated are all submitted to the REST authorization
>>> layer.
>>> The default authorizations now contains directives to allow
>>> unauthenticated
>>> requests to the various certificate terminus to allow new hosts.
>>> The conf/auth.conf file has been modified to match such defaults.
>>>
>>>
>>> Signed-off-by: Brice Figureau <[email protected]>
>>> ---
>>> conf/auth.conf                           |   33 +++++++++++++++++++ 
>>> ++
>>> lib/puppet/network/rest_authconfig.rb    |   21 ++++++++-----
>>> lib/puppet/network/rest_authorization.rb |   21 +-------------
>>> spec/unit/network/rest_authconfig.rb     |   25 +++++++++++-----
>>> spec/unit/network/rest_authorization.rb  |   46 +++++
>>> +-----------------------
>>> 5 files changed, 74 insertions(+), 72 deletions(-)
>>>
>>> diff --git a/conf/auth.conf b/conf/auth.conf
>>> index 784acc9..d42a347 100644
>>> --- a/conf/auth.conf
>>> +++ b/conf/auth.conf
>>> @@ -11,6 +11,7 @@
>>> # path /path/to/resource
>>> # [environment envlist]
>>> # [method methodlist]
>>> +# [auth[enthicated] {yes|no|on|off}]
>>> # allow [host|ip|*]
>>> # deny [host|ip]
>>> #
>>> @@ -24,6 +25,7 @@
>>> # path ~ regex
>>> # [environment envlist]
>>> # [method methodlist]
>>> +# [auth[enthicated] {yes|no|on|off}]
>>> # allow [host|ip|*]
>>> # deny [host|ip]
>>> #
>>> @@ -36,24 +38,35 @@
>>> # path ~ ^/path/to/resource
>>> # is essentially equivalent to path /path/to/resource
>>> #
>>> +# environment:: restrict an ACL to a specific set of environments
>>> +# method:: restrict an ACL to a specific set of methods
>>> +# auth:: restrict an ACL to an authenticated or unauthenticated
>>> request
>>> +#
>>>
>>> +### Authenticated ACL - those applies only when the client
>>> +### has a valid certificate and is thus authenticated
>>> +
>>> # allow nodes to drop and find their facts
>>> path /facts
>>> +auth yes
>>> method save,find
>>> allow *
>>>
>>> # allow all nodes to get their catalogs (ie their configuration)
>>> path /catalog
>>> +auth yes
>>> method find
>>> allow *
>>>
>>> # allow all nodes to access the certificates services
>>> path /certificate
>>> +auth yes
>>> method find
>>> allow *
>>
>> It *looks* like there's no default value for 'auth', but it might  
>> be a
>> good idea for it to default to 'yes'.  I expect most people will
>> assume that a given rule will only apply to authenticated users, so
>> it's probably safest to default to that, and then have this switch
>> that allows them to say that a given rule applies to unauthenticated
>> users.
>
> Yes, you're right it might make more sense. I'll change the default.
>
>>>
>>> # allow all nodes to store their reports
>>> path /report
>>> +auth yes
>>> method save
>>> allow *
>>>
>>> @@ -61,6 +74,26 @@ allow *
>>> # which means in practice that fileserver.conf will
>>> # still be used
>>> path /file
>>> +auth yes
>>> +allow *
>>> +
>>> +### Unauthenticated ACL, for clients for which the current master
>>> doesn't
>>> +### have a valid certificate
>>> +
>>> +# allow access to the master CA
>>> +path /certificate/ca
>>> +auth no
>>> +method find
>>> +allow *
>>> +
>>> +path /certificate/
>>> +auth no
>>> +method find
>>> +allow *
>>> +
>>> +path /certificate_request
>>> +auth no
>>> +method find, save
>>> allow *
>>>
>>> # this one is not stricly necessary, but it has the merit
>>> diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/
>>> network/rest_authconfig.rb
>>> index 22be8b0..2b15b96 100644
>>> --- a/lib/puppet/network/rest_authconfig.rb
>>> +++ b/lib/puppet/network/rest_authconfig.rb
>>> @@ -6,13 +6,16 @@ module Puppet
>>>        attr_accessor :rights
>>>
>>>        DEFAULT_ACL = {
>>> -            :facts =>   { :acl => "/facts", :method =>
>>> [:save, :find] },
>>> -            :catalog => { :acl => "/catalog", :method => :find },
>>> +            :facts =>   { :acl => "/facts", :method =>
>>> [:save, :find], :authenticated => true },
>>
>> I'm not sure 'save' should be in here by default; this basically
>> allows any host to get any other host's facts, and that's probably  
>> not
>> what people want.
>
> You mean find?
> You need save if you want to send the facts from the client to the
> server, isn't it?

Der, you're right, I meant 'find'.

I should be merging today the code that makes 'save' unnecessary, too,  
since Catalog.find will include the facts in the request.

>
> Note: I got those entries by trial an error. Starting with a fresh
> master and a fresh client (without certificate), allowing all what was
> denied until I could complete a full run. It is possible I missed a  
> few
> things or broaden the perm too much, though.

Ah.  That'd actually be great documentation - who needs exactly what  
rights. :)

>
>>>
>>> +            :catalog => { :acl => "/catalog", :method
>>> => :find, :authenticated => true },
>>
>> This is an interesting one - we've got code internally that does this
>> for us, but basically, hosts can't actually get any catalog other  
>> than
>> their own, even if they're theoretically authorized.  The compiler
>> terminus just ignores the request key, replacing it with the
>> authentication name.
>>
>> How hard would it be for this ACE to specify that hosts can only
>> retrieve their own catalogs?
>
> It's trivial, although the way the default entries are installed  
> doesn't
> allow it for the moment, but it's still a trivial change.
> In terms of auth.conf, it would be as easy as:
>
> path ~ ^\/catalog\/([^/]+)
> auth yes
> method find
> allow $1
>
> Thus only 'host.domain.com' can access to /catalog/host.domain.com and
> so on.
> Note: this can work only for authenticated requests, because the  
> reverse
> lookup of the ip address might not match the node name, so
> unauthenticated ACL of this type might not work.

That would be awesome; it would move some code out of the Catalog  
support classes to where it belongs: in the auth classes.

>
>> Then we could ditch the internal code that uses the authentication
>> name instead of the request, and people would get more useful errors
>> if they tried to do something that isn't currently allowed.
>
> Yes, but at the same time, if they override the rule in the auth.conf,
> this ACL might not be installed and the security of the system would  
> be
> really weak.
> I think I'll have to rework the default ACL system to be smarter to
> detect such weakened situation (note that I don't really see how, but
> you might have some ideas). At the moment, the system is pretty dumb.

I've generally playd caveat programmer here; we can guarantee the  
default permissions, but once you start screwing with them you better  
know what you're doing.

Without this move, people are limited and *can't* allow one host to  
retrieve another host's catalog, which isn't good either.

>
>> Man, it's *much* nicer having a real auth system. :)
>>
>> FTR, this is fine as is, but if we leave it, we should file a bug for
>> 0.26 to implement this.
>
> We can merge the next revision of this patch, and if I didn't  
> implement
> this, let's open a new ticket.

Sounds good.  This can easily wait, because it's a good refactor  
rather than a critical change or even a new feature.

>
>>>
>>>            # this one will allow all file access, and thus delegate
>>>            # to fileserver.conf
>>>            :file =>    { :acl => "/file" },
>>> -            :cert =>    { :acl => "/certificate", :method  
>>> => :find },
>>> -            :reports => { :acl => "/report", :method => :save }
>>> +            :cert =>    { :acl => "/certificate", :method
>>> => :find, :authenticated => true },
>>> +            :reports => { :acl => "/report", :method
>>> => :save, :authenticated => true },
>>> +            :cert_ca => { :acl => "/certificate/ca", :method
>>> => :find, :authenticated => false },
>>
>> Why is this extra entry necessary?
>>
>>>
>>> +            :get_cert =>{ :acl => "/certificate/", :method
>>> => :find, :authenticated => false },
>>> +            :cert_request =>{ :acl => "/
>>> certificate_request", :method => [:find, :save], :authenticated =>
>>> false },
>>>        }
>>>
>>>        def self.main
>>> @@ -32,7 +35,8 @@ module Puppet
>>>                                    :node => request.node,
>>>                                    :ip => request.ip,
>>>                                    :method => request.method,
>>> -                                    :environment =>
>>> request.environment)
>>> +                                    :environment =>
>>> request.environment,
>>> +                                    :authenticated =>
>>> request.authenticated)
>>>        end
>>>
>>>        def initialize(file = nil, parsenow = true)
>>> @@ -52,8 +56,8 @@ module Puppet
>>>        def insert_default_acl
>>>            DEFAULT_ACL.each do |name, acl|
>>>                unless rights[acl[:acl]]
>>> -                    Puppet.warning "Inserting default
>>> '#{acl[:acl]}' acl because none were found in '%s'" % ( @file || "no
>>> file configured")
>>> -                    mk_acl(acl[:acl], acl[:method])
>>> +                    Puppet.warning "Inserting default
>>> '#{acl[:acl]}'(%s) acl because none were found in '%s'" %
>>> [acl[:authenticated] ? "auth" : "non-auth" , ( @file || "no file
>>> configured")]
>>> +                    mk_acl(acl[:acl], acl[:method],
>>> acl[:authenticated])
>>>                end
>>>            end
>>>            # queue an empty (ie deny all) right for every other path
>>> @@ -62,7 +66,7 @@ module Puppet
>>>            rights.newright("/") unless rights["/"]
>>>        end
>>>
>>> -        def mk_acl(path, method = nil)
>>> +        def mk_acl(path, method = nil, authenticated = nil)
>>>            @rights.newright(path)
>>>            @rights.allow(path, "*")
>>>
>>> @@ -70,6 +74,7 @@ module Puppet
>>>                method = [method] unless method.is_a?(Array)
>>>                method.each { |m| @rights.restrict_method(path, m) }
>>>            end
>>> +            @rights.restrict_authenticated(path, authenticated)
>>> unless authenticated.nil?
>>>        end
>>>
>>>        def build_uri(request)
>>> diff --git a/lib/puppet/network/rest_authorization.rb b/lib/puppet/
>>> network/rest_authorization.rb
>>> index e6f62d9..1294a3d 100644
>>> --- a/lib/puppet/network/rest_authorization.rb
>>> +++ b/lib/puppet/network/rest_authorization.rb
>>> @@ -16,29 +16,10 @@ module Puppet::Network
>>>            @authconfig
>>>        end
>>>
>>> -        # Verify that our client has access.  We allow untrusted
>>> access to
>>> -        # certificates terminus but no others.
>>> +        # Verify that our client has access.
>>>        def check_authorization(request)
>>> -            if request.authenticated?
>>> -                authenticated_authorized?(request)
>>> -            else
>>> -                unless unauthenticated_authorized?(request)
>>> -                    msg = "%s access to %s [%s]" %
>>> [ (request.node.nil? ? request.ip : "#{request.node}
>>> (#{request.ip})"), request.indirection_name, request.method ]
>>> -                    Puppet.warning("Denying access: " + msg)
>>> -                    raise AuthorizationError.new( "Forbidden
>>> request:" + msg )
>>> -                end
>>> -            end
>>> -        end
>>> -
>>> -        # delegate to our authorization file
>>> -        def authenticated_authorized?(request)
>>>            authconfig.allowed?(request)
>>>        end
>>> -
>>> -        # allow only certificate requests when not authenticated
>>> -        def unauthenticated_authorized?(request)
>>> -            request.indirection_name == :certificate or
>>> request.indirection_name == :certificate_request
>>> -        end
>>>    end
>>> end
>>>
>>> diff --git a/spec/unit/network/rest_authconfig.rb b/spec/unit/
>>> network/rest_authconfig.rb
>>> index 7dc9738..7f6817a 100644
>>> --- a/spec/unit/network/rest_authconfig.rb
>>> +++ b/spec/unit/network/rest_authconfig.rb
>>> @@ -16,8 +16,8 @@ describe Puppet::Network::RestAuthConfig do
>>>        @acl = stub_everything 'rights'
>>>        @authconfig.rights = @acl
>>>
>>> -        @request = stub 'request', :indirection_name =>
>>> "path", :key => "to/resource", :ip => "127.0.0.1",
>>> -                                   :node => "me", :method
>>> => :save, :environment => :env
>>> +        @request = stub 'request', :indirection_name =>
>>> "path", :key => "to/resource", :ip => "127.0.0.1",
>>> +                                   :node => "me", :method
>>> => :save, :environment => :env, :authenticated => true
>>>    end
>>>
>>>    it "should use the puppet default rest authorization file" do
>>> @@ -33,7 +33,7 @@ describe Puppet::Network::RestAuthConfig do
>>>    end
>>>
>>>    it "should ask for authorization to the ACL subsystem" do
>>> -        @acl.expects(:fail_on_deny).with("/path/to/resource", :node
>>> => "me", :ip => "127.0.0.1", :method => :save, :environment => :env)
>>> +        @acl.expects(:fail_on_deny).with("/path/to/resource", :node
>>> => "me", :ip => "127.0.0.1", :method => :save, :environment
>>> => :env, :authenticated => true)
>>>
>>>        @authconfig.allowed?(@request)
>>>    end
>>> @@ -53,6 +53,11 @@ describe Puppet::Network::RestAuthConfig do
>>>            @acl.expects(:restrict_method).with(:path, :method)
>>>            @authconfig.mk_acl(:path, :method)
>>>        end
>>> +
>>> +        it "should restrict the ACL to a specific authentication
>>> state" do
>>> +
>>> @acl.expects(:restrict_authenticated).with(:path, :authentication)
>>> +            @authconfig.mk_acl(:path, nil, :authentication)
>>> +        end
>>>    end
>>>
>>>    describe "when parsing the configuration file" do
>>> @@ -70,7 +75,7 @@ describe Puppet::Network::RestAuthConfig do
>>>            @authconfig.rights.stubs(:[]).returns(true)
>>>            @authconfig.rights.stubs(:[]).with(acl).returns(nil)
>>>
>>> -            @authconfig.expects(:mk_acl).with { |a,m| a == acl }
>>> +            @authconfig.expects(:mk_acl).with { |a,m,auth| a ==  
>>> acl }
>>>
>>>            @authconfig.insert_default_acl
>>>        end
>>> @@ -96,14 +101,18 @@ describe Puppet::Network::RestAuthConfig do
>>>    describe "when adding default ACLs" do
>>>
>>>        [
>>> -            { :acl => "/facts", :method => [:save, :find] },
>>> -            { :acl => "/catalog", :method => :find },
>>> -            { :acl => "/report", :method => :save },
>>> +            { :acl => "/facts", :method =>
>>> [:save, :find], :authenticated => true },
>>> +            { :acl => "/catalog", :method => :find, :authenticated
>>> => true },
>>>            { :acl => "/file" },
>>> +            { :acl => "/certificate", :method
>>> => :find, :authenticated => true },
>>> +            { :acl => "/report", :method => :save, :authenticated
>>> => true },
>>> +            { :acl => "/certificate/ca", :method
>>> => :find, :authenticated => false },
>>> +            { :acl => "/certificate/", :method
>>> => :find, :authenticated => false },
>>> +            { :acl => "/certificate_request", :method =>
>>> [:find, :save], :authenticated => false }
>>>        ].each do |acl|
>>>            it "should create a default right for #{acl[:acl]}" do
>>>                @authconfig.stubs(:mk_acl)
>>> -                @authconfig.expects(:mk_acl).with(acl[:acl],
>>> acl[:method])
>>> +                @authconfig.expects(:mk_acl).with(acl[:acl],
>>> acl[:method], acl[:authenticated])
>>>                @authconfig.insert_default_acl
>>>            end
>>>        end
>>> diff --git a/spec/unit/network/rest_authorization.rb b/spec/unit/
>>> network/rest_authorization.rb
>>> index 3acd23f..db42dd7 100644
>>> --- a/spec/unit/network/rest_authorization.rb
>>> +++ b/spec/unit/network/rest_authorization.rb
>>> @@ -22,48 +22,22 @@ describe Puppet::Network::RestAuthorization do
>>>    end
>>>
>>>    describe "when testing request authorization" do
>>> -        describe "when the client is not authenticated" do
>>> -            before :each do
>>> -                @request.stubs(:authenticated?).returns(false)
>>> -            end
>>> +        it "should delegate to the current rest authconfig" do
>>> +
>>> @authconfig.expects(:allowed?).with(@request).returns(true)
>>>
>>> -            [ :certificate, :certificate_request].each do |
>>> indirection|
>>> -                it "should allow #{indirection}" do
>>> -
>>> @request.stubs(:indirection_name).returns(indirection)
>>> -                    lambda
>>> { @auth.check_authorization(@request) }.should_not raise_error
>>> Puppet::Network::AuthorizationError
>>> -                end
>>> -            end
>>> -
>>> -
>>> [ :facts
>>> , :file_metadata
>>> , :file_content, :catalog, :report, :checksum, :runner ].each do |
>>> indirection|
>>> -                it "should not allow #{indirection}" do
>>> -
>>> @request.stubs(:indirection_name).returns(indirection)
>>> -                    lambda
>>> { @auth.check_authorization(@request) }.should raise_error
>>> Puppet::Network::AuthorizationError
>>> -                end
>>> -            end
>>> +            @auth.check_authorization(@request)
>>>        end
>>>
>>> -        describe "when the client is authenticated" do
>>> -            before :each do
>>> -                @request.stubs(:authenticated?).returns(true)
>>> -            end
>>> -
>>> -            it "should delegate to the current rest authconfig" do
>>> -
>>> @authconfig.expects(:allowed?).with(@request).returns(true)
>>> -
>>> -                @auth.check_authorization(@request)
>>> -            end
>>> +        it "should raise an AuthorizationError if authconfig raises
>>> an AuthorizationError" do
>>> +
>>> @authconfig
>>> .expects
>>> (:allowed
>>> ?).with
>>> (@request
>>> ).raises(Puppet::Network::AuthorizationError.new("forbidden"))
>>>
>>> -            it "should raise an AuthorizationError if authconfig
>>> raises an AuthorizationError" do
>>> -
>>> @authconfig
>>> .expects
>>> (:allowed
>>> ?).with
>>> (@request
>>> ).raises(Puppet::Network::AuthorizationError.new("forbidden"))
>>> -
>>> -                lambda
>>> { @auth.check_authorization(@request) }.should raise_error
>>> Puppet::Network::AuthorizationError
>>> -            end
>>> +            lambda { @auth.check_authorization(@request) }.should
>>> raise_error Puppet::Network::AuthorizationError
>>> +        end
>>>
>>> -            it "should not raise an AuthorizationError if request
>>> is allowed" do
>>> -
>>> @authconfig.expects(:allowed?).with(@request).returns(true)
>>> +        it "should not raise an AuthorizationError if request is
>>> allowed" do
>>> +
>>> @authconfig.expects(:allowed?).with(@request).returns(true)
>>>
>>> -                lambda
>>> { @auth.check_authorization(@request) }.should_not raise_error
>>> Puppet::Network::AuthorizationError
>>> -            end
>>> +            lambda
>>> { @auth.check_authorization(@request) }.should_not raise_error
>>> Puppet::Network::AuthorizationError
>>>        end
>>>    end
>>> end
>>> \ No newline at end of file
>>> -- 
>>> 1.6.0.2
>>>
>>>
>>>>
>>
>
>
>
> >


-- 
Hollywood is a place where they'll pay you a thousand dollars for a
kiss and fifty cents for your soul. -- Marilyn Monroe
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to