SE-0378: Package Registry Authentication
- Proposal: SE-0378
- Author: Yim Lee
- Review Manager: Tom Doron
- Status: Implemented (Swift 5.8)
- Implementation: apple/swift-package-manager#5838
- Review: (pitch), (review), (acceptance)
Introduction
A package registry may require authentication for some or all of its API in order to identify user performing the action and authorize the request accordingly.
Motivation
Common authentication methods used by web services include basic authentication, access token, and OAuth. SwiftPM supports only basic authentication today, which limits its abilities to interact with package registry services.
Proposed solution
We propose to modify the swift package-registry command and registry configuration to add token authentication support. The changes should also ensure there is flexibility to add other authentication methods in the future.
The design draws inspiration from docker login and npm login, in that there will be a single command for user to verify and persist registry credentials.
Detailed design
Changes to swift package-registry command
Instead of the swift package-registry set subcommand and the --login and --password options as proposed in SE-0292 originally, we propose the new login and logout subcommands for adding/removing registry credentials.
New login subcommand
Log in to a package registry. SwiftPM will verify the credentials using the registry service's login API. If it returns a successful response, credentials will be persisted to the operating system's credential store if supported, or the user-level netrc file otherwise. The user-level configuration file located at ~/.swiftpm/configuration/registries.json will also be updated.
SYNOPSIS
swift package-registry login <url> [options]
OPTIONS:
--username Username
--password Password
--token Access token
--no-confirm Allow writing to netrc file without confirmation
--netrc-file Specify the netrc file path
--netrc Use netrc file even in cases where other credential stores are preferredurl should be the registry's base URL (e.g., https://example-registry.com). In case the location of the login API is something other than /login (e.g., https://example-registry.com/api/v1/login), provide the full URL.
The URL must be HTTPS.
The table below shows the supported authentication types and their required option(s):
| Authentication Method | Required Option(s) | | --------------------- | -------------------------- | | Basic | --username, --password | | Token | --token |
The tool will analyze the provided options to determine the authentication type and prompt (i.e., interactive mode) for the password/token if it is missing. For example, if only --username is present, the tool assumes basic authentication and prompts for the password.
For non-interactive mode, simply provide the --password or --token option as required or make sure the secret is present in credential storage.
If the operating system's credential store is not supported, the tool will prompt user for confirmation before writing credentials to the less secured netrc file. Use --no-confirm to disable this confirmation.
To force usage of netrc file instead of the operating system's credential store, pass the --netrc flag.
Example: basic authentication (macOS, interactive)
> swift package-registry login https://example-registry.com \
--username jappleseed
Enter password for 'jappleseed':
Login successful.
Credentials have been saved to the operating system's secure credential store.An entry for example-registry.com would be added to Keychain.
registries.json would be updated to indicate that example-registry.com requires basic authentication:
{
"authentication": {
"example-registry.com": {
"type": "basic"
},
...
},
...
}Example: basic authentication (operating system's credential store not supported, interactive)
> swift package-registry login https://example-registry.com \
--username jappleseed
Enter password for 'jappleseed':
Login successful.
WARNING: Secure credential store is not supported on this platform.
Your credentials will be written out to netrc file.
Continue? (Yes/No): Yes
Credentials have been saved to netrc file.An entry for example-registry.com would be added to the netrc file:
machine example-registry.com
login jappleseed
password alpineregistries.json would be updated to indicate that example-registry.com requires basic authentication:
{
"authentication": {
"example-registry.com": {
"type": "basic"
},
...
},
...
}Example: basic authentication (use netrc file instead of operating system's credential store, interactive)
> swift package-registry login https://example-registry.com \
--username jappleseed
--netrc
Enter password for 'jappleseed':
Login successful.
WARNING: You choose to use netrc file instead of the operating system's secure credential store.
Your credentials will be written out to netrc file.
Continue? (Yes/No): Yes
Credentials have been saved to netrc file.An entry for example-registry.com would be added to the netrc file:
machine example-registry.com
login jappleseed
password alpineregistries.json would be updated to indicate that example-registry.com requires basic authentication:
{
"authentication": {
"example-registry.com": {
"type": "basic"
},
...
},
...
}Example: basic authentication (operating system's credential store not supported, non-interactive)
> swift package-registry login https://example-registry.com \
--username jappleseed \
--password alpine \
--no-confirm
Login successful.
Credentials have been saved to netrc file.An entry for example-registry.com would be added to the netrc file:
machine example-registry.com
login jappleseed
password alpineregistries.json would be updated to indicate that example-registry.com requires basic authentication:
{
"authentication": {
"example-registry.com": {
"type": "basic"
},
...
},
...
}<a name="override-login-url"></a>
Example: basic authentication (operating system's credential store not supported, non-interactive, non-default login URL)
> swift package-registry login https://example-registry.com/api/v1/login \
--username jappleseed \
--password alpine \
--no-confirm
Login successful.
Credentials have been saved to netrc file.An entry for example-registry.com would be added to the netrc file:
machine example-registry.com
login jappleseed
password alpineregistries.json would be updated to indicate that example-registry.com requires basic authentication:
{
"authentication": {
"example-registry.com": {
"type": "basic",
"loginAPIPath": "/api/v1/login"
},
...
},
...
}Example: token authentication
> swift package-registry login https://example-registry.com \
--token jappleseedstokenAn entry for example-registry.com would be added to the operating system's credential store if supported, or the user-level netrc file otherwise:
machine example-registry.com
login token
password jappleseedstokenregistries.json would be updated to indicate that example-registry.com requires token authentication:
{
"authentication": {
"example-registry.com": {
"type": "token"
},
...
},
...
}New logout subcommand
Log out from a registry. Credentials are removed from the operating system's credential store if supported, and the user-level configuration file (registries.json).
To avoid accidental removal of sensitive data, netrc file needs to be updated manually by the user.
SYNOPSIS
swift package-registry logout <url>Changes to registry configuration
We will introduce a new authentication key to the user-level registries.json file, which by default is located at ~/.swiftpm/configuration/registries.json. Any package registry that requires authentication must have a corresponding entry in this dictionary.
{
"registries": {
"[default]": {
"url": "https://example-registry.com"
}
},
"authentication": {
"example-registry.com": {
"type": <AUTHENTICATION_TYPE>, // One of: "basic", "token"
"loginAPIPath": <LOGIN_API_PATH> // Optional. Overrides the default API path (i.e., /login).
}
},
"version": 1
}type must be one of the following:
basic: username and passwordtoken: access token
Credentials are to be specified in the native credential store of the operating system if supported, otherwise in the user-level netrc file. (Only macOS Keychain will be supported in the initial feature release; more might be added in the future.)
See credential storage for more details on configuring credentials for each authentication type.
Credential storage
SwiftPM will always use the most secure way to handle credentials on the platform. In general, this would mean using the operating system's credential store (e.g., Keychain on macOS). It falls back to netrc file only if there is no other solution available.
Basic Authentication
macOS Keychain
Registry credentials should be stored as "Internet password" items in the macOS Keychain. The "item name" should be the registry URL, including https:// (e.g., https://example-registry.com).
netrc file (if operating system's credential store is not supported)
A netrc entry for basic authentication looks as follows:
machine example-registry.com
login jappleseed
password alpineBy default, SwiftPM looks for netrc file in the user's home directory. A custom netrc file can be specified using the --netrc-file option.
Token Authentication
User can configure access token for a registry as similarly done for basic authentication, but with token as the login/username and the access token as the password.
For example, a netrc entry would look like:
machine example-registry.com
login token
password jappleseedstokenAdditional changes in SwiftPM
- Only the user-level netrc file will be used. Project-level netrc file will not be supported.
- SwiftPM will perform lookups in one credential store only. For macOS, it will be Keychain. For all other platforms, it will be the user-level netrc file.
- The
--disable-keychainand--disable-netrcoptions will be removed.
New package registry service API
A package registry that requires authentication must implement the new API endpoint(s) covered in this section.
login API
SwiftPM will send a HTTP POST request to this API to validate user credentials provided by the login subcommand.
The default API path is /login, but this can be overridden by providing the full API URL to the login subcommand.
The API request will include an Authorization HTTP header constructed as follows:
- Basic authentication:
Authorization: Basic <base64 encoded username:password> - Token authentication:
Authorization: Bearer <token>
The registry service must return HTTP status 200 in the response if login is successful, and 401 otherwise.
In case the registry service does not support an authentication method, it should return HTTP status 501.
SwiftPM will persist user credentials to local credential store if login is successful.
Security
This proposal moves SwiftPM to use operating system's native credential store (e.g., macOS Keychain) on supported platforms, which should yield better security.
We are also eliminating the use of project-level netrc file. This should prevent accidental checkin of netrc file and thus leakage of sensitive information.
Impact on existing packages
This proposal eliminates the project-level netrc file. There should be no other impact on existing packages.