-
Notifications
You must be signed in to change notification settings - Fork 23
fix(W-18094367): remove support for .netrc files #170
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
base: main
Are you sure you want to change the base?
Conversation
91211ec
to
90604a4
Compare
90604a4
to
61729c5
Compare
return path.join(home, os.platform() === 'win32' ? '_netrc' : '.netrc') | ||
} | ||
|
||
removeUnencryptedNetrcMachines() |
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 it's fine to include the code for deleting the old cleartext Netrc entries, but I do not agree that it should be automatically run on the initial release of the new storage mechanism. Let's put the activation/calls to removeUnencryptedNetrcMachines()
in a subsequent PR that can be released in a patch version after the major release.
If initial release of the new storage mechanism breaks something for a customer, they need to be able to CLI update back to the previous CLI version to restore functionality.
import {removeToken, retrieveToken} from './token-storage' | ||
|
||
// intentional side effect | ||
import '../scripts/cleanup-netrc.cjs' |
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.
Same comment as prior, that activation/calls to the clean-up code should be in a separate subsequent CLI release, after the major release implementing the new storage mechanism.
@@ -0,0 +1,313 @@ | |||
/** | |||
* Functions for securely storing bearer tokens using OS-level utilities. |
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 zero test coverage for these new functions.
} | ||
|
||
/** | ||
* Reads the contents of the config file and decrypts it using the provided bearer token |
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.
Not seeing test coverage of these functions either.
return crypto.pbkdf2Sync( | ||
bearerToken, | ||
salt, | ||
100000, |
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.
sec: number of iterations is too low - as per SFSS-074, this needs to be greater than or equal to 210,000 for HMAC-SHA512.
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.
Great feedback! I'll make that change.
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 NIST really goes for 600K but that's pretty overkill for SHA512.
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 using the credential storage makes sense but there is a minimal change in security posture: A local user who can read the .netrc file will also be able to execute heroku auth:token
.
} | ||
}) | ||
try { | ||
await writeConfigContents(config, entry.password) |
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 seems like something we should consider simplifying.
If I'm understanding it correctly, we're storing the username and token in a file and then encrypting the file with the token. The token is then stored in the operating system's credential storage.
Couldn't we store the username (which I think is mostly unnecessary) next to the token and avoiding having to maintain the encryption logic?
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 a great idea!
I originally had to provide affordances to automated processes that might need to read credentials directly from a file. It's not clear that the intent was to make this transient by looking at the source code alone. When this is rolled out, I'm hoping to include guidance for customers in this area while supporting a minimally disruptive solution until a full migration can take place.
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.
How will such processes read credentials from the file if it's encrypted?
|
||
export type ClientConfig = { | ||
'api.heroku.com': ConfigEntry, | ||
'git.heroku.com': ConfigEntry, |
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.
Applications directly accessing token storage need to be updated to use new methods
As far as I know, we don't implement a custom credential helper so Git relies on cURL reading this entry from the .netrc file.
@tt - Exactly. This was an area of consideration for me as well. I understand the best practice to be:
Thoughts? |
I think we should definitely implement these but it still doesn't change the fact that any user who can invoke the CLI can exfiltrate a token. We could make I'm not saying this because we shouldn't more in the direction of adopting better platform primitives. I only want us to recognize that we're making it more complicated to receive the plaintext token but not impossible. |
Do the "secure credential storage mechanisms" implemented by this change (Keychain Access, Windows Credential Manager, libsecret) prompt the user for a local password or biometric authentication, before saving or reading a credential? |
Not in any of the tests I performed. AFAIK, these tools are intended to be used in this way and shouldn't need explicit input from the user. |
W-18094367
Remove .netrc support in favor of modern encryption and OS-level key stores
This PR modernizes credential storage by removing
.netrc
file support and implementing secure credential storage using OS-level key stores and encryption.OS-Specific Keystore Implementation
The CLI now uses the following secure credential storage mechanisms:
sudo apt-get install libsecret-1-dev
sudo yum install libsecret-devel
sudo pacman -S libsecret
Breaking Changes
No longer writes or reads token data from
.netrc
filesNetrc
machines for 'api.heroku.com' and 'git.heroku.com' are explicitly removed - any existing .netrc files containing Heroku credentials will be permanently removed.Token storage implementation changes
retrieveToken()
andgetConfigContents()
Authorization: bearer ${this.heroku.auth}
can safely continue to do so.System Requirements
Linux Users:
HEROKU_API_KEY
env var must be usedmacOS Users:
Windows Users:
Testing Instructions