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

feat(rfc): externalized account management #159

Merged
merged 4 commits into from
Aug 14, 2020

Conversation

ncknt
Copy link
Member

@ncknt ncknt commented Jul 30, 2020

Consistent account management across services and providers in Spinnaker.

Copy link
Contributor

@ezimanyi ezimanyi left a comment

Choose a reason for hiding this comment

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

I think this would be great; both moving the repository to be per credential type (and thus strongly-typed) as well as making it possible to store these in some other persistence store.

I've definitely wanted for a while to move at least Kubernetes credentials to SQL for the reasons you enumerate here; this work would lay the groundwork for being able to do that.

Copy link
Member

@robzienert robzienert left a comment

Choose a reason for hiding this comment

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

I like the idea, long overdue.


Here's a simple implementation of the interfaces (interfaces should be in kork): [https://github.com/ncknt/clouddriver/compare/master...ncknt:feat/kork-accounts?expand=1](https://github.com/ncknt/clouddriver/compare/master...ncknt:feat/kork-accounts?expand=1)

An `Account` contains the information necessary to access an account. It can be parsed and enriched into a `Credentials`. Spinnaker already has a lot of these: e.g. `com.netflix.spinnaker.clouddriver.security.AccountCredentials`, `com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactCredentials`, `com.netflix.spinnaker.igor.service.BuildService`, etc.
Copy link
Member

Choose a reason for hiding this comment

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

I'm interested to hear more about why we need a distinction between Account and Credentials. Do we have an opportunity to simplify things?

Copy link
Member Author

Choose a reason for hiding this comment

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

Account is supposed to be a POJO that can be read from simple properties. Credentials is what services use and may also have injected beans (e.g. KubernetesNamedAccountCredentials use a factory. NetflixAmazonCredentials uses a AWSCredentialsProvider). By separating account source (list of props) from credentials, it becomes easy to create new sources without having to worry about what else is needed.

We could do without Account but then each time you want to add a new source, you have to deal with the wiring. Alternatively, we could move the beans outside of the Credentials object but that would be a bigger change.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think there's a lot of clumsiness in the current implementation and split between Account/Credentials. It's made deserialization tricky (number of places that need to be changed to introduce a new attribute) and has been a sore spot for some time now.

I don't think it's a terribly clean distinction right now, feels like the wiring could be handled but I've not looked through every implementation.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's true that there's a lot of repetition in some of the providers. Unless we do a major redesign and move all managed beans out of these credentials objects, we will always need to enrich these beans. The same class could optionally represent Account and Credentials and keeping the same abstraction, AccountLifecyleHandler could be in charge of autowiring beans. We could even provide a default handler that does the autowiring.

e.g.

class NetflixAmazonCredentials extends AmazonCredentials implements Account {
	...
}

class AwsLifecycleHandler extends AutowiringLifecycleHandler<NetflixAmazonCredentials> {
	
	accountAdded(NetflixAmazonCredentials cred) {
	    // autowire beans
		super.accountAdded(cred)
		// enable agents etc.
	} 
	...
}

I was hoping to start small (easy migration to Account and Credentials) and refactors could be kept for later.

Copy link
Member

@robzienert robzienert Aug 4, 2020

Choose a reason for hiding this comment

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

I was hoping to start small [...] and refactors could be kept for later.

This is a statement I've been seeing a lot more of lately in the community and I think we can do better. I appreciate the sentiment of making forward movement, but I have not seen frequent evidence that these smaller changes are followed up with a well-considered design and subsequent refactors. We definitely want to make incremental improvement and we definitely want to keep forward momentum; but more importantly, we want to make forward-looking, incremental improvement drawing on lessons we've already learned. We should be striving to make fundamental fixes with these proposals.

Account and credential management, especially in Clouddriver, have been long-identified as problematic and non-ergonomic, so I'm confused why we wouldn't want to tackle these problems immediately while we're in here, rather than copy-forward things we already know were mistakes. Doubly so, considering this code is a proposal for standardizing the way of representing account management across services, it's concerning we'd choose to implement the design knowing it isn't what we want.

Is there a path forward that we can think about what we want account and credential management to be, and work backwards from there to figure out what is possible in the short-term, and have a clear vision of where we're headed? I'd feel much more comfortable with incremental improvements when the destination is known, rather than incremental improvements without a known destination.

Copy link
Member Author

@ncknt ncknt Aug 4, 2020

Choose a reason for hiding this comment

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

I was hoping to start small (easy migration to Account and Credentials) and refactors could be kept for later.

In the context of the comment, I meant implement the described design in smaller incremental change (by provider) - the goal of the document doesn't change.

I didn't feel the RFC's stated goals would be controversial but I'm okay revising them. I just don't understand what I'm missing. Adam had a good point (perhaps we don't need Account and Credentials) and I was proposing a way forward that remains manageable.

We're committed to implementing the change for Kubernetes and AWS providers (likely more providers and services) but we won't do all services and providers so we need an intermediary solution.

it's concerning we'd choose to implement the design knowing it isn't what we want

I'm happy with the proposal. I took Adam's comment in the context of the AWS provider, because as far as I can tell other providers don't suffer from the many layers of abstractions that the AWS provider has.

Is there a path forward that we can think about what we want account and credential management to be, and work backwards from there [...] ?
I'd feel much more comfortable with incremental improvements when the destination is known

That's how I (think I) worked. I want each type of account to be independently managed, I want the effort to adopt this model to be low so all providers can get onboard, and I want to do it across services.

Edit: answered the actual questions

Copy link
Member

Choose a reason for hiding this comment

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

I have no issues with the proposed goals, like I said earlier, I think this is a great idea and long overdue! I misinterpreted the context of your comment, and thought we were taking a step back. I apologize.

It is unclear to me what the actual proposed work is for the AWS provider, since that one is evidently the worst provider in terms of account mgmt. I see a bunch of work done for Kubernetes in your branch, but nothing for AWS - are we planning to rip out what is there and improve, or add this stuff on top?

Copy link
Member Author

Choose a reason for hiding this comment

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

I just wanted to define the framework here and leave the discussion for each provider into the PRs that will ensue. I believe you (Netflix) have additional glue code around Spinnaker and I didn't want to break anyone's environment. But I'd be happy to simplify further.

Copy link
Contributor

@ajordens ajordens Aug 5, 2020

Choose a reason for hiding this comment

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

Things may get clearer in the draft PR if we can just be consistent about what we are calling things.

public interface CredentialsRepository<T extends Credentials> {
  T getOne(String key);
  Set<T> getAll();
  T save(String key, T account);
  T update(String key, T account);
  void delete(String key);
  String getType();
}

Minor point but potentially leading to confusion is the somewhat arbitrary use of credentials vs account. To someone not intimately familiar with the distinction, this will stand out. I think it's largely and easily correctable.

As a user of all this, I still don't think I should care about a distinction between Account and Credentials. Even more so looking across different Spinnaker services (clouddriver has made a mess of this but it's not as bad in igor right?).

I may be beating a dead horse here and not saying anything materially different than the actual intent of this work.

Copy link
Member Author

@ncknt ncknt Aug 5, 2020

Choose a reason for hiding this comment

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

That is a good point. I actually changed the words in the middle of the RFC - hence the remaining account in the code snippet above. I used to have Account as AccountProperties (and Credentials as Account).

Would AccountProperties (or AccountInformation) be more explicit? Naming things is hard.

Edit: I've updated the branch to be more consistent with the terms it uses.

- Create the default `CredentialsRepository` bean
- Create the default `CredentialsProvider` bean

`clouddriver-elasticsearch` uses credentials across credentials and can be changed to use a `CompositeCredentialsRepository` that federates `CredentialsRepository` with non migrated provider accounts.
Copy link
Member

Choose a reason for hiding this comment

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

uses credentials across credentials

I don't understand this part.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry there's a typo! What I meant is that the elasticsearch module seems to need to access credentials across providers - or at least it seemed to but I've never used it so it may very well be wrong. I took it as an exercise: what if we need to access multiple types of credentials? Hence the CompositeCredentialsRepository.

Copy link
Contributor

@ajordens ajordens Aug 4, 2020

Choose a reason for hiding this comment

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

The clouddriver-elasticsearch module may not be all that relevant, I don't believe it's widely/if-at-all used outside of Netflix for entity tags.

There is some lingering experimental indexing code that I suspect can be rm -rf'd ... years ago we experimented with maintaining a separate index in elasticsearch but it didn't go anywhere. Removing this code will likely simplify this composite conversation.


Here's a simple implementation of the interfaces (interfaces should be in kork): [https://github.com/ncknt/clouddriver/compare/master...ncknt:feat/kork-accounts?expand=1](https://github.com/ncknt/clouddriver/compare/master...ncknt:feat/kork-accounts?expand=1)

An `Account` contains the information necessary to access an account. It can be parsed and enriched into a `Credentials`. Spinnaker already has a lot of these: e.g. `com.netflix.spinnaker.clouddriver.security.AccountCredentials`, `com.netflix.spinnaker.clouddriver.artifacts.config.ArtifactCredentials`, `com.netflix.spinnaker.igor.service.BuildService`, etc.
Copy link
Contributor

@ajordens ajordens Aug 5, 2020

Choose a reason for hiding this comment

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

Things may get clearer in the draft PR if we can just be consistent about what we are calling things.

public interface CredentialsRepository<T extends Credentials> {
  T getOne(String key);
  Set<T> getAll();
  T save(String key, T account);
  T update(String key, T account);
  void delete(String key);
  String getType();
}

Minor point but potentially leading to confusion is the somewhat arbitrary use of credentials vs account. To someone not intimately familiar with the distinction, this will stand out. I think it's largely and easily correctable.

As a user of all this, I still don't think I should care about a distinction between Account and Credentials. Even more so looking across different Spinnaker services (clouddriver has made a mess of this but it's not as bad in igor right?).

I may be beating a dead horse here and not saying anything materially different than the actual intent of this work.


Spinnaker 1.23 (1.22?):
- Add interfaces to kork
- Start splitting AccountCredentialsRepository by provider (we'd now be able to use multiple accounts with the same name in different providers)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is that preferable? We've got a weird mix of APIs that sometimes include account + provider and sometimes just account.

Historically I've come down on the side of appreciating uniqueness in account names (but I could possibly be swayed here).

Copy link
Member Author

Choose a reason for hiding this comment

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

I couldn't spot where the account was ambiguous if you know the provider (or the implied provider based on where you are in Deck or in the code). It clearly doesn't mean there aren't any!

I think it is important to allow different providers to use different sources. For instance, some users store their Kubernetes account information in a separate system but have a handful of manually configured AWS accounts that remain in Spring props.

The non uniqueness is a by-product of having separate sources of accounts. Currently, I am not even sure what happens when the same name is used in the different providers. The last one wins? And it probably changes if accounts are dynamic. Regardless, it's probably not what the user expects.

IMO we should make sure that the provider is always displayed next to the account (in Deck) or present in the URL in APIs.

Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't think clouddriver would even allow you to have duplicate account names across providers (we have two cloud providers that ultimately land resources in the same aws account but we give them distinct names.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe you can configure it that way (in the yaml files) and I don't believe you'd get an error, just unexpected behavior.

@ncknt
Copy link
Member Author

ncknt commented Aug 6, 2020

One thing that I forgot to explicitly call out is that this is not just about creating accounts but also providing stable account management interfaces for users.

Yes we could simplify to: class MyCredentials { // autowired props and beans } but as soon as properties don't come from Spring, users will need to build MyCredentials objects and manually add the beans and there's no stability guarantee. I realize that this could also break if fields are removed from account definitions but that's very rarely happens.

That explains the AccountParser and CredentialsLifecycleHandler abstractions. In reality these are tiny interfaces.

I realize that there's also the issue of the AWS provider which has 5 credentials classes and a complicated loader mechanism. Ideally we'd keep that discussion for the AWS provider PR but for what it's worth, I share the concern that it doesn't look easily maintainable. I can also give it a try on that not-for-real PR if it helps clarify the end state.

@ajordens ajordens self-requested a review August 13, 2020 18:41
@ajordens
Copy link
Contributor

Putting the prototypical PR aside, I'm a +1 with this proposal. Let's do it!

I think any concerns I've had are more related to potential implementations that can be tackled at that point.

@robzienert
Copy link
Member

+1 on the proposal.

Copy link
Member

@plumpy plumpy left a comment

Choose a reason for hiding this comment

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

Yeah, I'd love to look into the PR more, but as a high-level proposal, this sounds like a great idea, thanks.

Copy link
Contributor

@ethanfrogers ethanfrogers left a comment

Choose a reason for hiding this comment

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

Sorry for the review delay. I didn't realize I was holding us up. +1, I'm excited to see this come to life as well.

@ajordens ajordens merged commit bf74bd7 into spinnaker:master Aug 14, 2020
@ncknt ncknt deleted the rfc/account-management branch August 14, 2020 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants