-
Notifications
You must be signed in to change notification settings - Fork 47
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 apt credentials parser #60
Conversation
e38fe87
to
f5faec1
Compare
caabf58
to
41e2547
Compare
3d20fa5
to
e708109
Compare
7a08f21
to
f9e2c5c
Compare
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.
Looks nice! I only have a few queries.
64391b9
to
1babeb0
Compare
Add apt.auth.conf(5) machine credentials parser. See the parser code for documentation. This commit doesn't change the current behavior. It's only a preparation for future commits that will add support for different types of archives.
1babeb0
to
f573944
Compare
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 Thomas, this is looking good. Here is a fisrt pass:
internal/archive/credentials.go
Outdated
token := p.scanner.Text() | ||
if i := strings.Index(token, "://"); i != -1 { | ||
if token[0:i] != p.query.scheme { | ||
return netrcInvalid, 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.
So this gets back to the start state? Again, the idea of the parser being in an invalid state and just going on is a bit confusing, and it seems to relate to the bug mentioned earlier as it just ignores the current context while looking for special keywords. We'll probably need to sync on this with more bandwidth.
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 is how the format is defined and parsed by Apt. It deliberately ignores current context. We're not at liberty to interpret the file differently.
internal/archive/credentials.go
Outdated
if !strings.HasPrefix(token, p.query.port) { | ||
return netrcInvalid, nil | ||
} | ||
token = token[len(p.query.port):] |
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 like it'll match the port ":90" when looking at ":9000".
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 check below mitigates that
if !strings.HasPrefix(p.query.path, token) {
return netrcInvalid, nil
}
Remove global comment.
Test findCredsInDir() instead of findCredentials(). The latter calls the former with the directory set to a CHISEL_AUTH_DIR environment variable value if it's non-empty or "/etc/apt/auth.conf.d".
Adorn error return value.
Report filename on parser error.
Report file path on os.Open() error too.
Skip files with invalid extension before stat()-ing them.
} | ||
var err error | ||
for state := netrcInvalid; state != nil; { | ||
state, err = state(&p) |
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.
that logic is true @woky.
Some questions though:
- if
err != nil
andstate == nil
, then do we need to continue to line 221? If not, better check theerr
here and justreturn
- as you say, the above condition is by design and
It would be a bug for a state to return both state and err non-nil.
. So isn't that the check you should be doing? I.e.if state != nil && err != 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.
Looks really great. I did a review just to help out and improve my Go skills :)
internal/archive/credentials.go
Outdated
func findCredentialsInDir(repoURL string, credsDir string) (creds credentials, err error) { | ||
contents, err := os.ReadDir(credsDir) | ||
if err != nil { | ||
if os.IsNotExist(err) { |
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.
Just an observation: here we are concluding that no credentials exist, which could be reached if CHISEL_AUTH_DIR
is set, but pointing to a non-existing directory. This may be by design, but just pointing out that this does not mean that /etc/apt/auth.conf.d
does not contain 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.
It is indeed by my design. :-) We did not specify this, so I did what felt intuitive to me. Warning when CHISEL_AUTH_DIR
doesn't exist and ignoring non-existent /etc/apt/auth.conf.d
feels inconsistent to me. Would you like a different behavior?
Example from elsewhere: mpv
looks up its configs in $MPV_HOME
or in ~/.config/mpv/
(among other locations). It doesn't complain if either of those doesn't exist. Similarly, git
doesn't complain when $GIT_CONFIG_SYSTEM
or /etc/gitconfig
doesn't exist but it reads it if it does. In both cases, if the variable is set (or non-empty?), the default location is skipped.
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 see both of your points. I think it would help to simply have a debug log message to say that CHISEL_AUTH_DIR
's path does not exist, regardless of its path.
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 does make sense to me, the naming of the environment variable suggests an overriding behaviour, which if abused, must yield consequences :)
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.
@cjdcordeiro @flotter I've added a debug message when the credentials directory does not exist, regardless of whether it's the default location or overridden by the variable. See commit 482ff15. Please resolve if you think it addresses your comment.
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.
As a good rule of thumb, any function that is requested to perform a given task must either succeed at its task, or return an error informing that it could not. That's even more true when the function actually returns an error, as the caller will imply that unless there's an error, its intention was successful. The task of this function is to find credentials in a directory, so it must either succeed at that, or return an error informing that it cannot perform its requested task.
The call site, though, may choose to ignore a given scenario. For handling some of those cases we have error results that can be more easily verified, either directly or via a 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.
Introduced ErrCredentialsNotFound
error. The function now returns this error when no credentials are found. @niemeyer Please resolve the thread if appropriate.
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.
Proposed changes looks great, thanks!
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.
Some additional notes.
internal/archive/credentials.go
Outdated
func findCredentialsInDir(repoURL string, credsDir string) (creds credentials, err error) { | ||
contents, err := os.ReadDir(credsDir) | ||
if err != nil { | ||
if os.IsNotExist(err) { |
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.
As a good rule of thumb, any function that is requested to perform a given task must either succeed at its task, or return an error informing that it could not. That's even more true when the function actually returns an error, as the caller will imply that unless there's an error, its intention was successful. The task of this function is to find credentials in a directory, so it must either succeed at that, or return an error informing that it cannot perform its requested task.
The call site, though, may choose to ignore a given scenario. For handling some of those cases we have error results that can be more easily verified, either directly or via a function.
internal/archive/credentials.go
Outdated
} | ||
if len(confFiles) == 0 { | ||
err = errors.Join(errs...) | ||
return |
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.
Similar to the earlier point on errors here: the loop above may skip all the way through, and we end up with no credentials and no error either.
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.
As I commented above, I've introduced ErrCredentialsNotFound error. The function now returns this error when no credentials are found. @niemeyer Please resolve the thread if appropriate.
internal/archive/credentials.go
Outdated
// directory specified by CHISEL_AUTH_DIR environment variable if it's | ||
// non-empty or /etc/apt/auth.conf.d. | ||
func findCredentials(repoUrl string) (credentials, error) { | ||
credsDir := "/etc/apt/auth.conf.d" |
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.
Per earlier comments, the default path should be a global variable, or a constant, according to your preference, and the logic must be tested according to these notes too. This function, and in particular the use of CHISEL_AUTH_DIR, looks untested?
internal/archive/credentials.go
Outdated
err = fmt.Errorf("cannot parse archive URL: %w", err) | ||
return | ||
} | ||
if query == nil { // creds.Empty() == 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.
This needs to be improved a bit so that the intention is more clear. If the point here is that we already have credentials, we should check creds explicitly instead of checking query and assuming credentials are set in a comment.
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.
Replaced with !creds.Empty()
. I can't rely on err != nil
here as that alone does not tell me whether the URL contained credentials. @niemeyer Please resolve the thread if appropriate.
internal/archive/credentials.go
Outdated
return | ||
} | ||
|
||
errs := make([]error, 0) |
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's the intention here? Why is this not just var errs []error
?
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.
As part of the change to return ErrCredentialsNotFound
when no credentials are found, non-fatal errors are now logged instead so the function no longer collects them, and this variable was removed. @niemeyer Please resolve the thread if appropriate.
internal/archive/credentials.go
Outdated
|
||
if err = findCredsInFile(query, f, &creds); err != nil { | ||
errs = append(errs, fmt.Errorf("cannot parse credentials file %s: %w", fpath, err)) | ||
} else if !creds.Empty() { |
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.
Let's please have a more clear interface on findCredsInFile, making it return the credentials when it finds them, as usual in Go, instead of having C-style output parameters unnecessarily. We should also not have to test creds for being empty. Per earlier notes, if the error is nil, the intent of the function must have been fulfilled.
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.
As I commented above, I've introduced ErrCredentialsNotFound
error. The function findCredsInFile
was renamed to findCredentialsInternal
and it now returns either a non-nil pointer to credentials or a non-nil error when an error occurs or credentials are not found. @niemeyer Please resolve the thread if appropriate.
internal/archive/credentials.go
Outdated
// [3] https://salsa.debian.org/apt-team/apt/-/blob/4e04cbaf/methods/aptmethod.h#L560 | ||
// [4] https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html | ||
// [5] https://daniel.haxx.se/blog/2022/05/31/netrc-pains/ | ||
func findCredsInFile(query *credentialsQuery, netrc io.Reader, creds *credentials) error { |
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.
Per note above, which of these is input and which of these is output? Let's please clean up the function prototype to reflect that, and have the error result reflect intent, per notes above.
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.
Commented about this function in the above thread. @niemeyer Please resolve the thread if appropriate.
1bcbb91
to
ddefc45
Compare
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 implementatino and the tuning!
// [2] https://salsa.debian.org/apt-team/apt/-/blob/d9039b24/apt-pkg/contrib/netrc.cc | ||
// [3] https://salsa.debian.org/apt-team/apt/-/blob/4e04cbaf/methods/aptmethod.h#L560 | ||
// [4] https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html | ||
// [5] https://daniel.haxx.se/blog/2022/05/31/netrc-pains/ |
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.
While we're still here, thanks for the extensive documentation and references above.
Add apt.auth.conf(5) machine credentials parser. See the parser code for
documentation.
This commit doesn't change the current behavior. It's only a preparation
for future commits that will add support for different types of archives.
This PR depends on #55.