-
Notifications
You must be signed in to change notification settings - Fork 50
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
Create private key files with read and write permissions for the user only #231
Conversation
I came across the 'mode' limitation in the Python os module on Windows platforms coming from the different access control implementations in Unix and Windows. It is documented only for the os.chmod function but it applies also for
Meaning that the same concepts for restricting Group/Other permissions do not apply on Windows. I added a workaround in the test case, however, I will be happy to understand the level of Windows support in securesystemslib. |
Now that we have abstract files and directories (#232) landed, let's figure out how to implement this change in the new world. I think it probably makes sense to have an optional argument to What do you think @sechkova ? |
An optional argument to |
Let me know what do you think. |
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.
4 months later I get to this :|
On Windows: I think it's ok to not support Windows ACLs as long as it's documented -- we're doing what we can with the file permissions available.
My only request for change is umask(), assuming it doesn't get much more complex with os.open(). The others are just comments.
securesystemslib/storage.py
Outdated
# When calculating file permissions, the current OS umask value is first | ||
# masked out. If custom permissions are set, we calculate a new umask | ||
# followed by a call to open() with default permissions (0o777). | ||
# This trick is done since open() does not support mode(permissions) as | ||
# input parameter, alternatively os.open() can be used. |
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 the os.open()
method is preferable if there are no other complications using it: os.umask()
changes umask for the whole process. This creates a race condition and could lead to bugs in a multithreaded application so should be avoided in a library.
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 had a look again. If we use os.open()
, its documentation says "When computing mode, the current umask value is first masked out." Meaning if we want to avoid race conditions caused by os.umask
, we can pass the desired permissions to os.open
but the resulting file permissions maybe restricted even further due to the applied mask.
This is not necessarily a bad thing for this concrete case where we want to restrict keys permissions. We can put the same line as a disclaimer in the docs.
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 comment seems to refer to the earlier version of the PR?
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.
Yep 🤦
securesystemslib/storage.py
Outdated
The permissions of the file-like object. If None, the | ||
implementation-specific default permissions apply. |
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 actual type of permissions
(int) should be specified, maybe with mention to constants in stat module
securesystemslib/util.py
Outdated
permissions: | ||
Custom file permissions for the newly created file. If None, the default | ||
OS permissions apply. |
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 actual type of permissions (int) should be specified, maybe with mention to constants in stat module
securesystemslib/interface.py
Outdated
@@ -69,6 +69,8 @@ | |||
# Supported key types. | |||
SUPPORTED_KEY_TYPES = ['rsa', 'ed25519'] | |||
|
|||
# Private keys are created with read and write permissions for the user only | |||
PRIVATE_KEY_MODE = 0o600 |
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 know some people prefer the octal form so I don't make any demands, just note that this could be
PRIVATE_KEY_MODE = stat.S_IRUSR | stat.S_IWUSR
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.
Yes, let's use the constants.
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.
Used!
A loong time has passed since opening this pull request ... let me resolve the conflicts and I will revive the thread. |
9929300
to
d66764e
Compare
I changed a bit the original idea. |
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 some comments.
Also, can you add a test (or modify an existing one) for the actual beef of the PR: that private keys are written with the correct permissions?
securesystemslib/storage.py
Outdated
# When calculating file permissions, the current OS umask value is first | ||
# masked out. If custom permissions are set, we calculate a new umask | ||
# followed by a call to open() with default permissions (0o777). | ||
# This trick is done since open() does not support mode(permissions) as | ||
# input parameter, alternatively os.open() can be used. |
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 comment seems to refer to the earlier version of the PR?
c4a8770
to
d0f880f
Compare
Pushed a new bunch of commits:
Related to overwriting keys, since this is the current behaviour:
|
Reworded the last commit 01431b5 |
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.
Code looks fine to me. I have two high level comments though (I'm sorry for not coming up with them earlier already).
-
StorageBackendInterface is supposedly abstract (I assume possibly not backed by a filesystem at all) but now we expect it to work with POSIX file permission -- should this have been a more abstract: "private/not private"? Or have a I misunderstood what the ABC is about?
-
The fact that the ABC method signature changes is a bit annoying: I think linters pick it up though so any out-of-tree implementations of the ABC should notice when they update SSLib (can anyone confirm from memory if linters check for the ABC method signature?)
Still, might be worth notifying the known implementors or even asking their opinion on the change. Does warehouse implement their own StorageBackendInterface?
Add an optional argument to StorageBackendInterface and FilesystemsBackend put() method for creating a file-object with custom mode (permissions). If custom permissions are not set, the implementation-specific default permissions apply. Signed-off-by: Teodora Sechkova <[email protected]>
Update generate_and_write_{rsa, ed25519, ecdsa}_keypair functions to create private keys with read and write permissions for the user only (0o600). Update util.persist_temp_file() with an optional permissions parameter. Signed-off-by: Teodora Sechkova <[email protected]>
Add a test case for persisting a file with custom permissions Signed-off-by: Teodora Sechkova <[email protected]>
Add test case checking that private key is generated with read and write permissions for user only. Signed-off-by: Teodora Sechkova <[email protected]>
Document as a side effect the overwriting of existing keys with the same filepath. Signed-off-by: Teodora Sechkova <[email protected]>
Rather than leak filesystem details to the user by exposing the file mode we add an abstract "restrict" boolean parameter. How the created object on the storage is restricted is an implementation detail of the StorageBackend. Suggested-by: Jussi Kukkonen <[email protected]> Signed-off-by: Joshua Lock <[email protected]>
Signed-off-by: Joshua Lock <[email protected]>
I just pushed updates to rebase this PR on master and address Jussi's first comment in #231 (review)
I changed the permissions parameter to a boolean |
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.
securesystemslib/storage.py
Outdated
fd = os.open(filepath, os.O_WRONLY|os.O_CREAT, | ||
stat.S_IRUSR|stat.S_IWUSR) | ||
else: | ||
# Use the default value (0o777) of the 'mode' argument of os.open() |
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.
Minor comment nits:
Is it worth to mention that passed and default permissions may be silently masked out by umask, or limited to 666 by windows? And is it worth to mention what 666 and 777 means?
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'll take a stab at including that info.
# stat.S_IFREG - Regular file | ||
expected_mode = stat.S_IFREG | stat.S_IRUSR | stat.S_IWUSR | ||
if os.name != 'posix': | ||
# Windows only supports setting the read-only attribute. |
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.
Is this comment accurate? Doesn't 666 mean rw-rw-rw?
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's really hard to locate and parse the docs around this.
Python's os.open()
points to the Windows docs for _open()
in their C Runtime which say that possible modes on Windows are:
pmode | Meaning |
---|---|
_S_IREAD | Only reading permitted. |
_S_IWRITE | Writing permitted. (In effect, permits reading and writing.) |
_S_IREAD | _S_IWRITE | Reading and writing permitted. |
which I read as being effectively 0o666 on Windows. Some additional confusion creeps in because Python's os.chmod()
docs state:
Note Although Windows supports chmod(), you can only set the file’s read-only flag with it (via the stat.S_IWRITE and stat.S_IREAD constants or a corresponding integer value). All other bits are ignored.
but I assume this means the mode cannot be changed from stat.S_IREAD
to stat.S_IWRITE
. However, I don't have access to a Windows box to verify. Nor do we have CI set up for Windows, so perhaps that (having Windows testing) is a blocker for this PR?
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 detailed research. Just ran the test on a windows box and it works. Having Windows CI does not seem to be a blocker for this PR.
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, great. I think I will push an extra commit to change the os.name != 'posix'
to os.name == 'nt'
for additional clarity (I don't expect anyone is using sslib on QNX 'java', but 🤷).
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.
Added that change as a suggestion https://github.com/secure-systems-lab/securesystemslib/pull/231/files#r988760854
@joshuagl, are you going to commit these changes, or are you waiting for me to press the button? :D |
Co-authored-by: Lukas Pühringer <[email protected]>
Oh, thanks for the nudge. Batched and committed. |
The incompatibility with newer securesystemslib versions was caused because of a new breaking change introduced in: secure-systems-lab/securesystemslib#231 Signed-off-by: Martin Vrachev <[email protected]>
The incompatibility with newer securesystemslib versions was caused because of a new breaking change introduced in: secure-systems-lab/securesystemslib#231 Signed-off-by: Martin Vrachev <[email protected]>
The incompatibility with newer securesystemslib versions was caused because of a new breaking change introduced in: secure-systems-lab/securesystemslib#231 Signed-off-by: Martin Vrachev <[email protected]>
The incompatibility with newer securesystemslib versions was caused because of a new breaking change introduced in: secure-systems-lab/securesystemslib#231 Signed-off-by: Martin Vrachev <[email protected]>
The incompatibility with newer securesystemslib versions was caused because of a new breaking change introduced in: secure-systems-lab/securesystemslib#231 Signed-off-by: Martin Vrachev <[email protected]>
The incompatibility with newer securesystemslib versions was caused because of a new breaking change introduced in: secure-systems-lab/securesystemslib#231 Signed-off-by: Martin Vrachev <[email protected]>
* Fix sslib new versions incompatibility bug The incompatibility with newer securesystemslib versions was caused because of a new breaking change introduced in: secure-systems-lab/securesystemslib#231 Signed-off-by: Martin Vrachev <[email protected]> * LocalStorage.put use restrict by default We want to use the "restrict" by default as we are the ones who will save metadata and target files through LocalStorage.put and we want to have as least privileges as possible because of security. Signed-off-by: Martin Vrachev <[email protected]> * Isort lint fix Signed-off-by: Martin Vrachev <[email protected]> * Update securesystemslib to 0.26.0 Signed-off-by: Martin Vrachev <[email protected]> * Make "restrict" true by default for IStorage.put() Signed-off-by: Martin Vrachev <[email protected]> * Remove StorageBackendInterface dependency Simplify IStorage by not inheriting StorageBackendInterface anymore. In the last securesystemslib version, the contributors there added a lot of functionality inside the StorageBackendInterface which we don't use and is irrelevant to us. Additionally, we had at least two cases where our tests failed because of StorageBackendInterface API changes and we were blocked to use an older securesystemslib version or update IStorage accordingly. Signed-off-by: Martin Vrachev <[email protected]> * Make IStorage an interface again By mistake I have made IStorage a regular class as opposed to an interface which is its purpose. Signed-off-by: Martin Vrachev <[email protected]> * Move write file logic of persist into IStorage.put Move the logic where we actually save the file content into the IStorage.put API call as this will help us easily support more storages within persist() call. Signed-off-by: Martin Vrachev <[email protected]> * Remove unused imports Signed-off-by: Martin Vrachev <[email protected]> Signed-off-by: Martin Vrachev <[email protected]>
Fixes issue #: #222
Description of the changes being introduced by the pull request:
Private key files should be created with read and write permissions for the user only (
0o600
).util.persist_temp_file()
takes an optional permissions argumentStorageBackendInterface.put()
andFilesystemsBackend.put()
interface.generate_and_write_{rsa, ed25519, ecdsa}_keypair
functions pass a permissions value0o600
when creating the private key fileNote: file permissions are calculated as default file mode (
0o777
) - umask (0o177
)Please verify and check that the pull request fulfils the following
requirements: