Skip to content

Create private key files with read and write permissions for the user only - #231

Merged
lukpueh merged 8 commits into
secure-systems-lab:masterfrom
sechkova:issue-222
Oct 11, 2022
Merged

Create private key files with read and write permissions for the user only #231
lukpueh merged 8 commits into
secure-systems-lab:masterfrom
sechkova:issue-222

Conversation

@sechkova

@sechkova sechkova commented Apr 16, 2020

Copy link
Copy Markdown
Contributor

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

coveralls commented Apr 16, 2020

Copy link
Copy Markdown

Coverage Status

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

@sechkova

sechkova commented Apr 16, 2020

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

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

@sechkova

sechkova commented Jun 5, 2020

Copy link
Copy Markdown
Contributor Author

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

@jku jku left a comment

Copy link
Copy Markdown
Collaborator

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 thread securesystemslib/storage.py Outdated
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
Copy Markdown
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.

@sechkova sechkova Nov 25, 2020

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep 🤦

Comment thread securesystemslib/storage.py Outdated
Comment on lines +90 to +91
The permissions of the file-like object. If None, the
implementation-specific default permissions apply.

Copy link
Copy Markdown
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 thread securesystemslib/util.py Outdated
Comment on lines +129 to +131
permissions:
Custom file permissions for the newly created file. If None, the default
OS permissions apply.

Copy link
Copy Markdown
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 thread securesystemslib/interface.py Outdated
SUPPORTED_KEY_TYPES = ['rsa', 'ed25519']

# Private keys are created with read and write permissions for the user only
PRIVATE_KEY_MODE = 0o600

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Used!

@sechkova

sechkova commented Nov 17, 2020

Copy link
Copy Markdown
Contributor Author

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
Comment thread securesystemslib/storage.py
@sechkova

sechkova commented Nov 27, 2020

Copy link
Copy Markdown
Contributor Author

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.

@jku jku left a comment

Copy link
Copy Markdown
Collaborator

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 thread securesystemslib/storage.py Outdated
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
Copy Markdown
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?

Comment thread securesystemslib/storage.py
Comment thread securesystemslib/storage.py
@sechkova
sechkova force-pushed the issue-222 branch 2 times, most recently from c4a8770 to d0f880f Compare December 7, 2020 14:56
@sechkova

sechkova commented Dec 7, 2020

Copy link
Copy Markdown
Contributor Author

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

sechkova commented Dec 7, 2020

Copy link
Copy Markdown
Contributor Author

Reworded the last commit 01431b5

@jku jku left a comment

Copy link
Copy Markdown
Collaborator

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?

Comment thread securesystemslib/interface.py Outdated
Comment thread tests/test_util.py Outdated
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
Copy Markdown
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

@lukpueh lukpueh left a comment

Copy link
Copy Markdown
Member

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

Comment thread 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()

Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread tests/test_util.py Outdated
Comment thread tests/test_util.py
# 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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

@joshuagl joshuagl Oct 6, 2022

Copy link
Copy Markdown
Collaborator

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
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment thread tests/test_util.py Outdated
Comment thread securesystemslib/storage.py Outdated
@lukpueh

lukpueh commented Oct 11, 2022

Copy link
Copy Markdown
Member

@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
Copy Markdown
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 pushed 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 pushed 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 pushed 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 pushed 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 pushed 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 pushed 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