Skip to content
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

Merged
merged 8 commits into from
Oct 11, 2022

Conversation

sechkova
Copy link
Contributor

@sechkova sechkova commented Apr 16, 2020

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 argument
  • add an optional permissions argument to StorageBackendInterface.put() and FilesystemsBackend.put()
  • interface.generate_and_write_{rsa, ed25519, ecdsa}_keypair functions pass a permissions value 0o600 when creating the private key file

Note: file permissions are calculated as default file mode (0o777) - umask (0o177)

Please verify and check that the pull request fulfils the following
requirements
:

  • The code follows the Code Style Guidelines
  • Tests have been added for the bug fix or new feature
  • Docs have been added for the bug fix or new feature

@coveralls
Copy link

coveralls commented Apr 16, 2020

Coverage Status

Coverage increased (+0.005%) to 98.794% when pulling 01431b5 on sechkova:issue-222 into d5aa8e6 on secure-systems-lab:master.

@sechkova
Copy link
Contributor Author

sechkova commented Apr 16, 2020

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 os.open and os.umask:

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.

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.

@sechkova sechkova changed the title Issue 222 Create private key files with read and write permissions for the user only Apr 16, 2020
@joshuagl
Copy link
Collaborator

Thanks for the quality submission @sechkova.

Unfortunately, I think we need to hold this PR for a while until we figure out if/how we can implement this functionality in a system with abstracted filesystems as proposed in PR #232

@joshuagl
Copy link
Collaborator

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 securesystemslib.storage. StorageBackendInterface.put() that modifies the on-disk permissions. Implementers of StorageBackendInterface can choose what to do with this optional parameter, for example they might choose to store the permissions in their storage system's metadata and apply them at get() time? Or they might choose to ignore them.

What do you think @sechkova ?

@sechkova
Copy link
Contributor Author

An optional argument to StorageBackendInterface.put() makes sense to me too + applying the changes from this PR on the FilesystemBackend implementation.

@sechkova
Copy link
Contributor Author

sechkova commented Jun 5, 2020

Let me know what do you think.
And my Windows concerns from the comment above remain unchanged 🙂

Copy link
Collaborator

@jku jku left a 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.

Comment on lines 232 to 253
# 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.
Copy link
Collaborator

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.

Copy link
Contributor Author

@sechkova sechkova Nov 25, 2020

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.

Copy link
Collaborator

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep 🤦

Comment on lines 90 to 91
The permissions of the file-like object. If None, the
implementation-specific default permissions apply.
Copy link
Collaborator

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

Comment on lines 129 to 131
permissions:
Custom file permissions for the newly created file. If None, the default
OS permissions apply.
Copy link
Collaborator

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

@@ -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
Copy link
Collaborator

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

Copy link
Collaborator

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used!

@sechkova
Copy link
Contributor Author

sechkova commented Nov 17, 2020

A loong time has passed since opening this pull request ... let me resolve the conflicts and I will revive the thread.

@sechkova sechkova force-pushed the issue-222 branch 2 times, most recently from 9929300 to d66764e Compare November 27, 2020 18:48
@sechkova
Copy link
Contributor Author

sechkova commented Nov 27, 2020

I changed a bit the original idea.
As pointed in #231 (comment) os.unmask is not recommended for a library so I decided to wrap os.open. The desired permissions are set but umask is masked out first.
This makes the user responsible of calling os.umask if needed.

Copy link
Collaborator

@jku jku left a 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?

Comment on lines 232 to 253
# 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.
Copy link
Collaborator

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?

securesystemslib/storage.py Outdated Show resolved Hide resolved
securesystemslib/storage.py Show resolved Hide resolved
@sechkova sechkova force-pushed the issue-222 branch 2 times, most recently from c4a8770 to d0f880f Compare December 7, 2020 14:56
@sechkova
Copy link
Contributor Author

sechkova commented Dec 7, 2020

Pushed a new bunch of commits:

  • adding test for creating private keys with restricted permissions
  • fixing wrong comments and pointing to os.open mode documentation in the docstrings

Related to overwriting keys, since this is the current behaviour:

@sechkova
Copy link
Contributor Author

sechkova commented Dec 7, 2020

Reworded the last commit 01431b5

Copy link
Collaborator

@jku jku left a 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).

  1. 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?

  2. 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?

securesystemslib/interface.py Outdated Show resolved Hide resolved
tests/test_util.py Outdated Show resolved Hide resolved
sechkova and others added 6 commits September 21, 2022 17:47
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 <tsechkova@vmware.com>
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 <tsechkova@vmware.com>
Add a test case for persisting a file with custom permissions

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
Add test case checking that private key is generated
with read and write permissions for user only.

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
Document as a side effect the overwriting of existing keys
with the same filepath.

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
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 <jkukkonen@google.com>
Signed-off-by: Joshua Lock <jlock@vmware.com>
Signed-off-by: Joshua Lock <jlock@vmware.com>
@joshuagl
Copy link
Collaborator

I just pushed updates to rebase this PR on master and address Jussi's first comment in #231 (review)

StorageBackendInterface is supposedly abstract

I changed the permissions parameter to a boolean restrict which lets implementing StorageBackendInterface's determine how best to interpret what restricted means. For FileSystemBackend the implementation sets the permissions of the file to 0o600 when restrict=True.

@joshuagl joshuagl requested review from jku and lukpueh October 3, 2022 11:05
Copy link
Member

@lukpueh lukpueh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, @sechkova and @joshuagl! Code looks good. I have two minor suggestions and one more important question inline...

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()
Copy link
Member

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?

Copy link
Collaborator

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.

tests/test_util.py Outdated Show resolved Hide resolved
# 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.
Copy link
Member

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?

Copy link
Collaborator

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?

Copy link
Member

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.

Copy link
Collaborator

@joshuagl joshuagl Oct 6, 2022

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 🤷).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests/test_util.py Outdated Show resolved Hide resolved
@lukpueh
Copy link
Member

lukpueh commented Oct 11, 2022

@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 <luk.puehringer@gmail.com>
@joshuagl
Copy link
Collaborator

@joshuagl, are you going to commit these changes, or are you waiting for me to press the button? :D

Oh, thanks for the nudge. Batched and committed.

@lukpueh lukpueh merged commit 57fbcdd into secure-systems-lab:master Oct 11, 2022
MVrachev added a commit to MVrachev/repository-service-tuf-worker that referenced this pull request Jan 5, 2023
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 <mvrachev@vmware.com>
MVrachev added a commit to MVrachev/repository-service-tuf-worker that referenced this pull request Jan 9, 2023
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 <mvrachev@vmware.com>
MVrachev added a commit to MVrachev/repository-service-tuf-worker that referenced this pull request Jan 9, 2023
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 <mvrachev@vmware.com>
MVrachev added a commit to MVrachev/repository-service-tuf-worker that referenced this pull request Jan 11, 2023
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 <mvrachev@vmware.com>
MVrachev added a commit to MVrachev/repository-service-tuf-worker that referenced this pull request Jan 18, 2023
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 <mvrachev@vmware.com>
MVrachev added a commit to MVrachev/repository-service-tuf-worker that referenced this pull request Jan 21, 2023
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 <mvrachev@vmware.com>
kairoaraujo pushed a commit to repository-service-tuf/repository-service-tuf-worker that referenced this pull request Jan 23, 2023
* 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 <mvrachev@vmware.com>

* 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 <mvrachev@vmware.com>

* Isort lint fix

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>

* Update securesystemslib to 0.26.0

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>

* Make "restrict" true by default for IStorage.put()

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>

* 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 <mvrachev@vmware.com>

* 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 <mvrachev@vmware.com>

* 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 <mvrachev@vmware.com>

* Remove unused imports

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants