- 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)
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.
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.
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.
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.
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 preferred
url
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.
> 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"
},
...
},
...
}
> 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 alpine
registries.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 alpine
registries.json
would be updated to indicate that example-registry.com
requires basic authentication:
{
"authentication": {
"example-registry.com": {
"type": "basic"
},
...
},
...
}
> 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 alpine
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, 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 alpine
registries.json
would be updated to indicate that example-registry.com
requires basic authentication:
{
"authentication": {
"example-registry.com": {
"type": "basic",
"loginAPIPath": "/api/v1/login"
},
...
},
...
}
> swift package-registry login https://example-registry.com \
--token jappleseedstoken
An 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 jappleseedstoken
registries.json
would be updated to indicate that example-registry.com
requires token authentication:
{
"authentication": {
"example-registry.com": {
"type": "token"
},
...
},
...
}
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>
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.
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.
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
).
A netrc entry for basic authentication looks as follows:
machine example-registry.com
login jappleseed
password alpine
By default, SwiftPM looks for netrc file in the user's
home directory. A custom netrc file can be specified using
the --netrc-file
option.
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 jappleseedstoken
- 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-keychain
and--disable-netrc
options will be removed.
A package registry that requires authentication must implement the new API endpoint(s) covered in this section.
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.
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.
This proposal eliminates the project-level netrc file. There should be no other impact on existing packages.