-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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 basic auth to kubernetes provider #1488
Conversation
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.
LGTM apart from the one small spelling error
provider/kubernetes/kubernetes.go
Outdated
return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName) | ||
} | ||
if secret == nil { | ||
return nil, errors.New("secret data must nost be nil") |
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.
-nost- not
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.
LGTM 👼
@alpe squash and rebase please ;-)
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.
Left a few comments.
I notice that most of the unhappy paths are not test-covered. Can we improve here? You already added the necessary secret stub.
provider/kubernetes/kubernetes.go
Outdated
return nil, err | ||
} | ||
if len(basicAuthCreds) == 0 { | ||
return nil, errors.New("secet file without credentials") |
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.
Typo (secet -> secret)
provider/kubernetes/kubernetes.go
Outdated
} | ||
} else if authType != "" { | ||
return nil, fmt.Errorf("unsupported auth-type: %q", authType) | ||
} |
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 we please put the entire block in a separate function?
loadIngresses
is already pretty big.
provider/kubernetes/kubernetes.go
Outdated
if len(basicAuthCreds) == 0 { | ||
return nil, errors.New("secet file without credentials") | ||
} | ||
} else if authType != "" { |
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 do this check before the happy path so that we don't need the else
?
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 condition was authType != "" && strings.ToLower(authType) == "basic"
for the error. The else block made sense but it's better in the extracted method.
provider/kubernetes/kubernetes.go
Outdated
} | ||
if len(secret.Data) != 1 { | ||
return nil, errors.New("secret must contain single element only") | ||
} |
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 block lends itself to a switch statement IMHO.
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 order of the conditions matter. The switch works top down when multiple conditions are true. So that's fine but it feels more fragile that I'll add note to keep the order.
provider/kubernetes/kubernetes.go
Outdated
return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName) | ||
} | ||
if secret == nil { | ||
return nil, errors.New("secret data must not be nil") |
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 rephrase this without the nil
for the user? What does a nil
secret imply?
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.
Why can this be nil anyway when ok
is true? I was wondering a lot when I ran into this but I did not spent time investigating.
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.
My guess is it won't ever be nil
unless ok
is false or err
is non-nil. The pointer is probably for performance / in-place-update reasons.
provider/kubernetes/kubernetes.go
Outdated
return nil, errors.New("secret data must not be nil") | ||
} | ||
if len(secret.Data) != 1 { | ||
return nil, errors.New("secret must contain single element only") |
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.
Improvement suggestion: "multiple secrets are not supported".
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.
Data can have 0 elements, too. I think this is a temporary solution until we have some convention for a key name or pattern.
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.
Ok.
provider/kubernetes/kubernetes.go
Outdated
for _, v := range secret.Data { | ||
firstElement = v | ||
break | ||
} |
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.
Why not firstElement := secret.Data[0]
? We have already asserted there's only a single element.
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.
Data
is a map
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.
Ah true, missed that. 👍
provider/kubernetes/kubernetes.go
Outdated
if len(secret.Data) != 1 { | ||
return nil, errors.New("secret must contain single element only") | ||
} | ||
var firstElement []byte |
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.
Variable name is not ideal. Maybe firstSecret
instead?
actual = provider.loadConfig(*actual) | ||
got := actual.Frontends["basic/auth"].BasicAuth | ||
if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) { | ||
t.Fatalf("unexpected creadentials: %+v", got) |
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.
Typo.
@@ -2121,6 +2278,19 @@ func (c clientMock) GetService(namespace, name string) (*v1.Service, bool, error | |||
return nil, false, nil | |||
} | |||
|
|||
func (c clientMock) GetSecret(namespace, name string) (*v1.Secret, bool, error) { | |||
if c.apiServiceError != nil { |
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 should not reuse apiServiceError
but get its own field.
I have applied most of the review comments @timoreimann requested. Thanks for the feedback! The test coverage and documentation can be better. I may be able to do a little bit more end of the week. |
Thanks @alpe, looking pretty good now. A bit of documentation (especially with regards to the limitations mentioned in the PR) and coverage of the important unhappy paths would be great to have. 👍 |
Ping @alpe |
Documentation added |
5f0836a
to
97e0be3
Compare
@alpe @timoreimann I know this is done but I was reviewing due to #1596 and saw this:
Was excluding the new |
Side question: What do we need the I'm really not too deep into that part of the code. What I know though is that we use various events as a trigger to poll the k8s APIs. Does the |
The traefik controller shall have access to secrets for the k8s basic authentication (traefik#1488) to work
The traefik controller shall have access to secrets for the k8s basic authentication (#1488) to work
@alpe These instuctions
are not on the traefik website and it was very hard to eventually track down this PR and find the example for creating the secret. |
@shadycuz Apologies, this could have been better. I had very limited time to work on this when I contributed the code. I'm happy that you find it useful. I am very sure the community will appreciate it when you could help with some doc so that it is easier for others to setup the basic auth. |
@alpe I can do that from what I know and what I see in this PR, but I have other questions, for example can we generate the secret according to instructions for generating the web dashboard auth?
as long as its |
Probably not the right place to post this, please tell me somewhere else that is more appropriate... I think it is problematic to grant permission to rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
resourceNames:
- "traefik-*" A small constraint on the user's secret name would make it a lot safer to run in the kube-system namespace. |
@erkolson Could ask your question on the Traefik community Slack channel or Stack Overflow using the "traefik" tag |
This will add the kubernetes provider for basic authentication. Thanks to @SantoDE for #1147.
Some constraints:
traefik
default(*) otherwise we can have some conventions here like key name (pattern)
Example
Based on https://docs.traefik.io/user-guide/kubernetes/
htpasswd -c ./auth jane
kubectl --namespace=kube-system create secret generic mysecret --from-file auth
Note the two annotations to enable basic auth and link the secret