-
Notifications
You must be signed in to change notification settings - Fork 222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Fixes #23211 - Token based PuppetCA autosigning #576
Conversation
178c986
to
cde95ae
Compare
Please do not remove old api endpoints, log a warning about them being deprecated instead. We usually keep api for one or two releases after the deprecation announcement. |
modules/puppetca/puppetca_api.rb
Outdated
get "/autosign" do | ||
content_type :json | ||
post "/autosign" do | ||
params = JSON.parse(request.env["rack.input"].read) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the api is being changed, why not change it to something like:
post "/autosign/:csr"
instead of JSON encoding and then decoding a single parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure that CSRs are not URL-safe, as they include slashes and line-breaks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could UR-encode the parameter? Alternatively, instead of putting the parameter in the url, you could put it in the body of the request (would still need to be url-encoded though), which is a bit cleaner.
@csr.attributes.map do |attr| | ||
{ | ||
oid: attr.oid, | ||
value: attr.value.value.first.value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks fragile: are all of the intermediate calls guaranteed to not return nils?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be fine. Openssl is not known for its nice code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, would probably be better to catch possible errors from this.
modules/puppetca/puppetca_main.rb
Outdated
logger.debug "Request did not include a CSR" | ||
return false | ||
end | ||
return true if signall |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is signall
needed? If so, please put warnings both when it's triggered and during initialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
signall is the equivalent of putting *
in your autosign.conf at the moment. As there are currently people using this it was a requested feature, so we're adding this as a setting. I don't think we need warnings for this as it is disabled by default and only is used if you manually update the settings, so you need to know what you're doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a warning would be a good thing -- people forget to switch things back all the time. This flag completely circumvents token authorization, a warning would be a nice reminder (if verbose).
modules/puppetca/puppetca_main.rb
Outdated
v =~ /^\s*#.*|^$/ ## Remove comments and empty lines | ||
end.map do |v| | ||
v.chomp ## Strip trailing spaces | ||
def foreman_callback token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you use something more descriptive for the method name, something like csr_recognised?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
modules/puppetca/puppetca_main.rb
Outdated
def foreman_callback token | ||
logger.debug "Calling foreman with token '#{token}'" | ||
fr = Proxy::HttpRequest::ForemanRequest.new | ||
req = fr.request_factory.create_delete("api/puppetca_token", :id => token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggested splitting the check and cleanup into two separate actions: not only removal of data on check not something that is expected, but it might make sense to keep the token until the host has been successfully created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing the token is part of the design. For security reasons, it should just be able to use the token once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what scenario do you think would it make sense to still have the token after the CSR was verified with it? If anything goes wrong during the verification, the token will not be deleted, so we just deleted it when the host has his cert signed, when we don't need the token anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It felt to me that this is a call with a (big) side-effect, which also broke the pattern of how we use "delete" method in smart-proxy.
If you renamed foreman_callback
to something like delete_token
, this would resolve the whole side-effect issue.
modules/puppetca/puppetca_main.rb
Outdated
return true | ||
end | ||
rescue => e | ||
logger.debug "Error while calling foreman: " + e.to_s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please log this at the error level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will change that. Do you think that the other logs are correct on debug? Considering you normally don't run it on debug-loglevel, you won't be able to trace back why a CSR wasn't verified if you wanted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok either way, but perhaps a warning would be a bit more helpful.
I would also suggest replacing all class-methods in |
@@ -20,6 +20,12 @@ def create_get(path, query={}, headers={}) | |||
req | |||
end | |||
|
|||
def create_delete(path, query={}, headers={}) | |||
req = Net::HTTP::Delete.new(uri(path).path + "/" + query[:id]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tap
might come in handy here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the same as in all the other functions there...
@csr.attributes.map do |attr| | ||
{ | ||
oid: attr.oid, | ||
value: attr.value.value.first.value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be fine. Openssl is not known for its nice code.
modules/puppetca/puppetca_main.rb
Outdated
autosign.puts certname unless found | ||
end | ||
logger.debug "Added #{certname} to autosign" | ||
def signall |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename this to sign_all
? I always read it as signal
and start wondering where the second l
comes from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
modules/puppetca/puppetca_main.rb
Outdated
def foreman_callback token | ||
logger.debug "Calling foreman with token '#{token}'" | ||
fr = Proxy::HttpRequest::ForemanRequest.new | ||
req = fr.request_factory.create_delete("api/puppetca_token", :id => token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing the token is part of the design. For security reasons, it should just be able to use the token once.
@witlessbird Outsourcing Methods: I will take a look at this. Also some comments inline. |
Don't worry, [test] failures are unrelated. |
modules/puppetca/puppetca_main.rb
Outdated
req = fr.request_factory.create_delete("api/puppetca_token", :id => token) | ||
begin | ||
response = fr.send_request(req) | ||
if response.code.to_i == 204 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say this is too specific, any 2xx would be ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we write the Foreman-API at the same time, why not be specific?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Foreman side of things can change which would require this code to be updated. Since we don't care about particulars of the response as long as it is an "ok" (aka 2xx) one, making it too specific makes it more fragile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@juliantodt: Thanks. Some more comments that came up when I read the code. The DI stuff looks good now.
# parameter is csr to use | ||
def autosign csr | ||
if csr.nil? | ||
logger.warn "Request did not include a CSR" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: missing .
modules/puppetca/puppetca_csr.rb
Outdated
@@ -0,0 +1,25 @@ | |||
module Proxy::PuppetCa | |||
class CertificateRequest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class name should match the name of the file.
If old behavior cannot be preserved at all, then all of these changes must reside in a new module/provider. |
@witlessbird: Why is that? Do we want to continue to support the old behavior so that users can use a newer smart-proxy version with an older Foreman version? Or is this meant for all the standalone users who use smart-proxy without Foreman? |
Removes old autosigning endpoints and adds new ones that take a incoming CSR from puppet, extract the token and forward it to foreman for verfication.
Took care of all the comments as well as the test failures. @witlessbird I can see that removing the old functionality might seem problematic, but it will be same with the updates to the foreman-core and I don't think we need to support older foreman versions with newer smart-proxy versions. Also allowing both signing options to be chosen by the user (-> will need to allow this in the foreman-core as well), will just cause problems like foreman & smart-proxy using tokens but puppet is configured to use the autosign.conf. When we are strict with these new changes and remove old functions we get obvious error messages at misconfigurations and the solution will be to just update to the latest version. I know it's not perfect to make these changes that affect multiple components that are backwards-incompatible, but I prefer it over the alternatives. |
I agree with @witlessbird that the API shouldn't change behavior of endpoints. If it changes, then it should use other URLs so Foreman gets a clear error. There are good reasons for this: until now we've always been flexible with versions. Running a 1.17 proxy with a 1.16 foreman worked, but also a 1.16 proxy with a 1.17 foreman. We know there are users with many proxies and upgrading them all at once is not feasible. That said, I think there's still room for a compatible API. The one that's currently changed is |
What @ekohl said. There is also a number of people who use smart-proxy api for integration with their own software -- they'll be affected by such a break too. |
@witlessbird @ekohl So what you're saying is that you don't necessarily have a problem with removing old endpoints in favour of new ones but with keeping some and changing how they work. So change the name of the /autosign endpoint and done? |
@juliantodt: not quite. Old endpoints have to keep names and preserve functionality for a couple releases after being declared as deprecated. If new functionality is being added to an existing end-point, the old behaviour must also be preserved. |
@juliantodt: It looks like there is a lot of resistance in getting this in, which is unfortunate as we did ask if there were any concerns before starting the implementation. I'd suggest to split this up and try to get the refactorings in (moving the code to a provider) so the puppetca api is pluggable. Maybe we can then either support both providers or move this to a plugin. |
Closing this in favour of the pluggable, backwards compatible approach #586. |
Removes old autosigning endpoints and adds new ones
that take a incoming CSR from puppet, extract the token
and forward it to foreman for verfication.
See also PRs in foreman-core, community-templates, puppet-foreman_proxy & puppet-puppet.
We would like to see some feedback early on while we are testing this thoroughly.