Skip to content
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

Add safer data-handling in ConjurCA #1688

Merged
merged 1 commit into from
Jul 16, 2020
Merged

Conversation

liavyona
Copy link
Contributor

What does this PR do?

In order to avoid NoMethodError that can be raised when the ConjurCA relevant resources is missing/have no value, added custom exceptions which inform the proper error.

What ticket does this PR close?

resolves #1315

Checklists

  • Reproduce the bug by deploy current version
  • Deploy a new env with the these changes
  • Test authn k8s works fine
  • Test the new exceptions raised when ConjurCa has malformed data.

Change log

  • This PR does not include user-facing changes and doesn't require a CHANGELOG update

private

def self.fetch_secret
@fetch_secret ||= ::Conjur::FetchRequiredSecrets.new
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's give this variable the same name as the class it represents - @fetch_required_secrets

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

begin
fetch_secret.(resource_ids: [ca_info.cert_id, ca_info.key_id])
rescue Errors::Conjur::RequiredResourceMissing
raise Errors::Authentication::AuthnK8s::CaResourceMissing, resource_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is resource_id in this context? what will be printed to the log?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - which log message will be the most helpful for the user?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which message will give them the best pin-pointed action item?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor exceptions messages

@@ -14,12 +14,28 @@ def self.create(resource_id)
#
def self.ca(resource_id)
ca_info = ::Conjur::CaInfo.new(resource_id)
stored_cert = Resource[ca_info.cert_id].last_secret.value
stored_key = Resource[ca_info.key_id].last_secret.value
stored_cert, stored_key = fetch_ca_secrets(ca_info, resource_id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is quite risky, as it assumes the order of the retrieval. what happens if someone will change the behaviour of FetchRequiredSecrets so that it retrieves the secrets in parallel? In this case the first secret in the array may not be stored_cert. How can you overcome this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Copy link
Contributor

@uCatu uCatu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIP

@@ -306,6 +306,16 @@ module AuthnK8s
msg: "Invalid Kubernetes host id: {0}. Must end with <namespace>/<resource_type>/<resource_id>",
code: "CONJ00048E"
)

CaResourceMissing = ::Util::TrackableErrorClass.new(
msg: "Resource {0-resource-id} doesn't have saved resource for cert_id or key_id ",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have validation for log messages from Docs or is it not needed here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From where are we getting the resource-id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the creation of the exception


CaResourceMissing = ::Util::TrackableErrorClass.new(
msg: "Resource {0-resource-id} doesn't have saved resource for cert_id or key_id ",
code: "CONJ00063E"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those error codes need to be documeneted in a KB/Troubleshooting guide?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not at the moment. there is an effort led by @netacoral but i am not sure that it has started.

@@ -14,12 +14,28 @@ def self.create(resource_id)
#
def self.ca(resource_id)
ca_info = ::Conjur::CaInfo.new(resource_id)
stored_cert = Resource[ca_info.cert_id].last_secret.value
stored_key = Resource[ca_info.key_id].last_secret.value
stored_cert, stored_key = fetch_ca_secrets(ca_info, resource_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reasoning for using oneliner?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

ca_cert = OpenSSL::X509::Certificate.new(stored_cert)
ca_key = OpenSSL::PKey::RSA.new(stored_key)
::Util::OpenSsl::CA.new(ca_cert, ca_key)
end

class << self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we want to use this architacutre of class + private? Is it TDD - can we write tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

raise Errors::Authentication::AuthnK8s::CaResourceMissing, ca_resource_id
rescue Errors::Conjur::RequiredSecretMissing
raise Errors::Authentication::AuthnK8s::CaResourceValueMissing, ca_resource_id

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line detected at begin body end.

end

def self.fetch_ca_resource(ca_resource_id)
begin
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant begin block detected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def self.fetch_ca_resource(ca_resource_id)
      begin
        fetch_required_secrets.(resource_ids: [ca_resource_id])[ca_resource_id]
      rescue Errors::Conjur::RequiredResourceMissing
        raise Errors::Authentication::AuthnK8s::CaResourceMissing, ca_resource_id
      rescue Errors::Conjur::RequiredSecretMissing
        raise Errors::Authentication::AuthnK8s::CaResourceValueMissing, ca_resource_id
      end
 end

@orenbm When I delete the begin it has errors with the indentation of rescue (and to fix this they need to be in the same indent as def which basically won't work). What's the right way?

Copy link
Member

@orenbm orenbm Jul 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should do it:

def self.fetch_ca_resource(ca_resource_id)
  fetch_required_secrets.(resource_ids: [ca_resource_id])[ca_resource_id]
rescue Errors::Conjur::RequiredResourceMissing
  raise Errors::Authentication::AuthnK8s::CaResourceMissing, ca_resource_id
rescue Errors::Conjur::RequiredSecretMissing
  raise Errors::Authentication::AuthnK8s::CaResourceValueMissing, ca_resource_id
end

Comment on lines 17 to 18
stored_cert = fetch_ca_resource(ca_info.cert_id)
stored_key = fetch_ca_resource(ca_info.key_id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will make 2 calls to the DB instead of 1 so it's not the ideal solution. I would retrieve them together and then take each var from the retrieved list. you can see how we did it in FetchAuthenticatorSecrets.

Comment on lines 31 to 34
rescue Errors::Conjur::RequiredResourceMissing
raise Errors::Authentication::AuthnK8s::CaResourceMissing, ca_resource_id
rescue Errors::Conjur::RequiredSecretMissing
raise Errors::Authentication::AuthnK8s::CaResourceValueMissing, ca_resource_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you see value in raising the error CaResourceValueMissing instead of the error RequiredResourceMissing? what value does the user get from this message that they don't get from the original?

orenbm
orenbm previously approved these changes Jul 15, 2020
Copy link
Member

@orenbm orenbm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful!

Care to fix your git history?

@orenbm
Copy link
Member

orenbm commented Jul 15, 2020

@liavyona you will also need to rebase on master

@@ -14,12 +14,16 @@ def self.create(resource_id)
#
def self.ca(resource_id)
ca_info = ::Conjur::CaInfo.new(resource_id)
stored_cert = Resource[ca_info.cert_id].last_secret.value
stored_key = Resource[ca_info.key_id].last_secret.value
ca_secrets = fetch_required_secrets.(resource_ids: [ca_info.cert_id, ca_info.key_id])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repos::ConjurCA#self.ca calls 'ca_info.cert_id' 2 times

@@ -14,12 +14,16 @@ def self.create(resource_id)
#
def self.ca(resource_id)
ca_info = ::Conjur::CaInfo.new(resource_id)
stored_cert = Resource[ca_info.cert_id].last_secret.value
stored_key = Resource[ca_info.key_id].last_secret.value
ca_secrets = fetch_required_secrets.(resource_ids: [ca_info.cert_id, ca_info.key_id])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repos::ConjurCA#self.ca calls 'ca_info.key_id' 2 times

Add fetch_required_secrets to handle malformed ca

There are 3 types of malformed ca data:
a. No resource - The ca has no key/cert saved on the db.
b. No value - The resource exists but has an empty value (not initialized yet).
c. Bad value  - The value is not the right one.
The added function is meant to take care of the first two cases by raising relevant exception to each case. The third one is being taken care by the OpenSSL library.
@liavyona liavyona force-pushed the malformed-conjurca-handling branch from d738424 to 65b26a6 Compare July 16, 2020 06:32
@liavyona
Copy link
Contributor Author

Beautiful!

Care to fix your git history?

Done

@liavyona
Copy link
Contributor Author

@liavyona you will also need to rebase on master

Done

@codeclimate
Copy link

codeclimate bot commented Jul 16, 2020

Code Climate has analyzed commit 65b26a6 and detected 0 issues on this pull request.

The test coverage on the diff in this pull request is 14.2% (50% is the threshold).

This pull request will bring the total coverage in the repository to 86.2% (0.0% change).

View more on Code Climate.

Copy link
Contributor

@uCatu uCatu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments are totally personal taste, up to you.
LGTM well done

stored_key = Resource[ca_info.key_id].last_secret.value
ca_cert_id = ca_info.cert_id
ca_key_id = ca_info.key_id
ca_secrets = fetch_required_secrets.(resource_ids: [ca_cert_id, ca_key_id])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ca_secrets = fetch_required_secrets.(resource_ids: [ca_cert_id, ca_key_id])
ca_secrets = fetch_required_secrets.(resource_ids: [ca_info.cert_id, ca_info.key_id])

ca_cert_id = ca_info.cert_id
ca_key_id = ca_info.key_id
ca_secrets = fetch_required_secrets.(resource_ids: [ca_cert_id, ca_key_id])
stored_cert = ca_secrets[ca_cert_id]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
stored_cert = ca_secrets[ca_cert_id]
stored_cert = ca_secrets[ca_info.cert_id]

ca_key_id = ca_info.key_id
ca_secrets = fetch_required_secrets.(resource_ids: [ca_cert_id, ca_key_id])
stored_cert = ca_secrets[ca_cert_id]
stored_key = ca_secrets[ca_key_id]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
stored_key = ca_secrets[ca_key_id]
stored_key = ca_secrets[ca_info.key_id]

@liavyona liavyona merged commit cfb781e into master Jul 16, 2020
@ismarc ismarc deleted the malformed-conjurca-handling branch April 13, 2022 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

Repos::ConjurCA doesn't raise a proper error when required secrets are empty
3 participants