-
Notifications
You must be signed in to change notification settings - Fork 475
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 namespace flag to secrets handlers #530
Conversation
handlers/secrets.go
Outdated
} | ||
|
||
if lookupNamespace == "kube-system" { | ||
return "", fmt.Errorf("unable to list within the kube-system namespace"), http.StatusUnauthorized |
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 we can update this error message to something like .. unable to manage secrets within the kube-system namespace
. which is related to what this endpoint is trying to do.
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.
Agreed and changed 💃
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.
Yes, but for every place it occurs, not just the one message.
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.
What would the other place be?
^ Hmm, is there an alternative way you could do it then? It sounds like you are not convinced that it's the right solution? cc @LucasRoesler |
Given that the namespace is an input, and we can only have one, why would you say we need it as an output? I'm not saying it's right or wrong, I'm curious about the design. |
I'm ok with this solution. Another one would be to do the query parameter verification in the GET handler and the POST verification in the other parameter, which would involve generate the kube client inside each parameter, which would change a lot. But I think the solution is ok enough to not justify all these changes. Would love ur input @LucasRoesler! |
I added because in a client – like the CLI – is probably easier to build return data listing the secret name and namespace if the result is in the body. |
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.
Two suggestions, one addresses the comment from the PR description about parsing the request body. I would recommend that change because it should help generalize the method. The other is a bit bigger of a design suggestion and probably warrants further discussion.
handlers/secrets.go
Outdated
} | ||
} else { | ||
body, _ := ioutil.ReadAll(r.Body) | ||
req := types.Secret{} |
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 also use an anonymous struct here, which will possibly make it more flexible
req := struct{ Namespace string }{}
This method could then be used for any POST/PUT/PATCH request that has a Namespace
field in the body.
In that case, you could use a switch statement on r.Method
to handle all of these cases
switch r.Method {
case http.MethodPost, http.MethodPut, http.Patch:
// load req body
default:
// load from GET param
}
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 like this, will implement!
handlers/secrets.go
Outdated
r.Body = ioutil.NopCloser(bytes.NewBuffer(body)) | ||
} | ||
|
||
if req.Namespace == "kube-system" { |
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 is not just kube-system
but the list of namespaces should be actively limited to those with a certain annotation. See
faas-netes/handlers/namespaces.go
Lines 31 to 66 in 5bb7c6b
func list(defaultNamespace string, clientset *kubernetes.Clientset) []string { | |
listOptions := metav1.ListOptions{} | |
namespaces, err := clientset.CoreV1().Namespaces().List(listOptions) | |
set := []string{} | |
// Assume that an error means that a Role, instead of ClusterRole is being used | |
// the Role will not be able to list namespaces, so all functions are in the | |
// defaultNamespace | |
if err != nil { | |
log.Printf("Error listing namespaces: %s", err.Error()) | |
set = append(set, defaultNamespace) | |
return set | |
} | |
for _, n := range namespaces.Items { | |
if _, ok := n.Annotations["openfaas"]; ok { | |
set = append(set, n.Name) | |
} | |
} | |
if !findNamespace(defaultNamespace, set) { | |
set = append(set, defaultNamespace) | |
} | |
return set | |
} | |
func findNamespace(target string, items []string) bool { | |
for _, n := range items { | |
if n == target { | |
return true | |
} | |
} | |
return false | |
} |
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 is yet another issue. We have checks for the kube-system
namespace in various place, would need a slightly bigger refactor.
Could you open one?
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 am not sure I agree. This change allows the user to modify secrets in namespaces that it isn't allowed to deploy functions to. There is already code for getting and checking the list of allowed. The refactor here would be to call the list
method and then, in the if
block, use the findNamepsace
method. In fact, you could combine those into a single statement, so it could even be a one line change to the PR.
Refactoring the other checks can definitely be done in a separate PR, but I don't think we should ignore it here especially when there is a drop in solution already implemented.
But, I am ok with merging this if @alexellis wants to get the functionality in first and refactor later
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.
Oh yes, I meant opening a PR for the other areas, I'll quickly add the check for this function
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.
Awesome and thanks!
handlers/secrets.go
Outdated
default: | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
} | ||
} | ||
|
||
// getLookupNamespace returns the namespace specified in any request type | ||
func getLookupNamespace(defaultNamespace string, r *http.Request) (string, error, int) { |
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 general, I would prefer the handler to decide which error code to return instead. One, I generally expect error
to be the last item returned from a method
func getLookupNamespace(defaultNamespace string, r *http.Request) (string, error) { }
And second, I think the separation of concerns is cleaner when a handler catches errors and then decides what the appropriate code is.
This method could return a sentinel error (see https://dave.cheney.net/2019/06/10/constant-time for an example if you are not familiar) for each error case and then have the handler can easily use a switch to decide the correct status code. This would then be cleaner for us to reuse the code in the future in cases that are not handlers, for example, in the openfaas-operator
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.
Thanks for the advice and the article, really good read :)
I refactored the code so the handler catches the error, based on the error string (is there a better way than checking the string?).
@LucasRoesler thank you for the abstraction suggestion. I would like to get logs and secrets working e2e and then for someone to refactor with an abstraction like this afterwards in a follow-up PR. |
I prefer to merge PRs that do only one thing, i.e. one from below:
|
@@ -65,7 +111,8 @@ func getSecretsHandler(kube typedV1.SecretInterface, w http.ResponseWriter, r *h | |||
secrets := []types.Secret{} | |||
for _, item := range res.Items { | |||
secret := types.Secret{ | |||
Name: item.Name, | |||
Name: item.Name, | |||
Namespace: item.Namespace, |
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 line is incorrect and is why the tests are currently failing, the faas-provider
needs to be updated, the latest version is 0.12.0
. This needs to be updated in the Gopkg.toml
and then you need to run dep ensure
again
// Secret for underlying orchestrator
type Secret struct {
Name string `json:"name"`
Value string `json:"value,omitempty"`
}
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.
@jonatasbaldin PTAL
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.
Done.
handlers/secrets.go
Outdated
default: | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
} | ||
} | ||
|
||
// getLookupNamespace returns the namespace specified in any request type | ||
func getLookupNamespace(defaultNamespace string, r *http.Request) (string, error) { | ||
req := struct{ Namespace string }{Namespace: defaultNamespace} |
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 am finding this confusing. I know Lucas suggested it, but why do we need this over a named type?
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 allows you to unmarshal any request that contains a Namespace field, and not just one particular request type.
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 mainly because we didn't have the Secret
type from faas-provider
with the new Namespace
field. Now we do. Will update it.
@@ -198,3 +197,71 @@ func Test_SecretsHandler_ListEmpty(t *testing.T) { | |||
t.Errorf(`want empty list to be valid json i.e. "[]", but was %q`, string(body)) | |||
} | |||
} | |||
|
|||
func Test_GetLookupNamespace(t *testing.T) { | |||
defaultNamespace := "openfaas-fn" |
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.
Would this work as a test-case table instead? Example: https://blog.alexellis.io/golang-writing-unit-tests/
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 can look into refactoring into table tests, let's just get everything right first 💃
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.
Sure, you can do it via this PR, or you could raise an issue now to track it and do it immediately after. I don't mind which.
This last commit has the changes needed to check the allowed namespace list. First, I needed to change the signature from the And in the tests, I'm testing against an allowed namespace – created with the |
Thank you for your contribution. I've just checked and your commit doesn't appear to be signed-off. That's something we need before your Pull Request can be merged. Please see our contributing guide. |
We can't take a merge commit via the rebase command, please can you revert that last change and apply it with a rebase instead?
|
In other words every commit has to be signed off and merge commits cannot be signed off. Undo the merge:
Rebase from master
Fix the conflicts:
Then push back up
|
Thanks @alexellis, I hope this output is the expected (looks like a mess) hehehe |
Why does it look like a mess? No, I was just trying to help you revert your unsigned merge commit. |
I think we are ready to squash these commits into one and then merge, that's something you can do at your end @jonatasbaldin Btw I can't see why this "is a mess"? @LucasRoesler thoughts? |
No worries man. I was joking because the PR looked a mess after the rebase because all the commits were listed again, I didn't know this was a thing. Just a 😂 reaction to the output hehehe Everything is fine 🤙 I can squash the commits a bit later, ok? Very happy we are almost merging this 🎉✨ |
Signed-off-by: Jonatas Baldin <[email protected]>
Squashed ✅ |
If we merge this first, I will rebase my PR #532 afterwards because there are a couple of places that they overlap |
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.
Approved.
Thank you for your patience @jonatasbaldin and for the awesome PR to faas-netes. Keep them coming And when you get a moment, checkout the ask on the unit test table, does it make sense? |
Thanks for all the feedback @alexellis and @LucasRoesler ❤️ @alexellis will put in my backlog for this week :) |
Thank you @jonatasbaldin |
Description
Add support to Namespace in Secrets.
I created the
getLookupNamespace
function, which I'm not 100% proud of, since it reads ther.Body
and puts it back into ther
struct. BUT this way I didn't need to change all the signatures of all the other handlers, also, if there's any verification for the namespace we can do in one place.Depends on (build will fail before merging the PRs below and bumping their version in this project):
Motivation and Context
How Has This Been Tested?
Added tests ✨ Also I deployed locally and tested with the following commands:
Yay! ❤️
Types of changes
Checklist:
git commit -s