diff --git a/_drafts/2023-10-31-rse-2023-common-pyos-questions.md b/_drafts/2023-10-31-rse-2023-common-pyos-questions.md deleted file mode 100644 index e6273c63..00000000 --- a/_drafts/2023-10-31-rse-2023-common-pyos-questions.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -layout: single -title: "Questions about pyOpenSci - RSE meeting 2023" -excerpt: "Here you will find answers to two of the most common questions that people ask about pyOpenSci. These questions were asked at our birds of a feather session at the 2023 Research Software Engineer meeting." -author: "Leah Wasser" -permalink: /blog/common-pyOpenSci-questions-rse-2023.html -header: - overlay_color: "#666" - overlay_filter: 0.6 -categories: - - blog-post - - community -toc: true -comments: true ---- - -In [a previous post](/pyOpenSci-research-software-2023.html), I discussed some of the issues that Research Software Engineers -(RSE) encounter in the Python packaging ecosystem. The issues were brought up -in a Birds of a Feature community session that I lead at the RSE meeting in -Chicago. - -Here, i'll address the two most common questions that we get about -pyOpenSci as asked in the RSE meeting. - -## How is pyOpenSci different from JOSS? - -One of the most common questions I get is: - -> What is the difference between pyOpenSci and JOSS? - -The short answer to this is that our organizations have different, -but complementary goals. Hence our partnership. - -JOSS's core goal is to provide the critical academic credit to software maintainers for developing tools. As such they are a publisher and publication is an end-point for a maintainer. Once a package is published in JOSS, it gains a CROSS-REF doi which is citable and attached to your ORCID-id. - -pyOpenSci's core goal is to support maintainers through the process of both developing and maintaining software. We also care about long term maintenance of software. As such, once accepted into our ecosystem, pyOpenSci package maintainers become an integral part of our community. pyOpenSci will build relationships with them. And we will watch tools over time to ensure tools in our growing catalog are maintained over time. - -If they become unmaintained we will either archive / sunset the project in our catalog and/or try to help find a new maintainer for the tool. - -| pyOpenSci | JOSS | -| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| Is a diverse community focused on both running peer review, developing Python packaging guidelines and mentorship and training. | Is a organization with a community-driven review and publication process | -| Cares about long-term maintenance of software and partners with JOSS for academic credit | Cares about academic credit for software and is a publication end-point | -| Reviews software that drives open science- broad scope | reviews software that is explicitly determined to be "research". As such has a more narrow scope. | -| Supports community-affiliated review | Has a fixed review process because it operates as a Journal publication | - -The good news is that you don't have to chose between us! - -### pyOpenSci-JOSS partnership for the win - -
- Image showing our peer review process.Broadly, there are 4 boxes at the top that show the process where first a maintaining submits a tool for review. then the package is reviewed by 2 reviewers. When a package is accepted, ie becomes a part of our ecosystem and then can move on to be published by JOSS if it is in scope for JOSS. -
Through our partnership with JOSS you can be reviewed by pyOpenSci and then published by JOSS (if in scope) via our review. Thus if you are interested in both maintained, high quality Python software and also long term maintenance of tools, this partnership is for you!
-
- -Through our partnership with JOSS, a maintainer can both get -support and contribute back to the growing pyOpenSci community. But they can also receive the software publication and associated academic credit that is so important. - -In the above image you may notice that we also run a affiliated package partnership program. We get -questions about that program often so i'll discuss that next. - -## pyOpenSci's partnership program with domain-specific software communities - -pyOpenSci runs an affiliated package [partnership program with domain specific Python communities](https://www.pyopensci.org/partners.html). - -We have a partnership program where we add an additional layer to our review where through a single end-to-end review process, a package can become: - -1. part of our ecosystem -2. published by JOSS -3. an affiliated package for pyOpenSci - -Some of the communities that we are currently speaking with include: - -- astropy -- scverse -- PyHeliophysics -- Sunpy -- plasmapy - -
- - Image with text that reads - affiliated package partnerships. It then has logos of the astropy, scverse, PyHeliophysics, Sunpy and plasmapy communities. Below it says  says your community here. - -
Add caption. -
-
- -Questions we received around our partner community program - -### Can a package that doesn't make the affiliated "standard" and is thus rejected from a partnership community or a package that doesn't want to go through JOSS< only pyOpenSci still become a part of the community? - -The answer to that is YES and yes. just because a package is submitted to pyOpenSci doesn't mean you HAVE to move forward with a publication. Further if your tool doesn't make the affiliated package cut, you can still be accepted into the pyOpenSci ecosystem - -in that regard it's a win-win situation for maintainers so submit to us regardless of their goals associated with JOSS or becoming an affiliated package with a partner community. diff --git a/_pages/volunteer.md b/_pages/volunteer.md index 90621a36..e35baef8 100644 --- a/_pages/volunteer.md +++ b/_pages/volunteer.md @@ -10,7 +10,7 @@ header: overlay_image: images/headers/pyopensci-learn-header.png overlay_filter: 0.3 volunteer-mission: - - excerpt: "The vibrant and diverse pyOpenSci community is driven by volunteer Pythonistas that care deeply about the scientific Python open source software that drives open science." + - excerpt: "pyOpenSci is a volunteer community that broadens participation in scientific open source. We make finding, sharing and contributing to reusable code easier for everyone, everywhere." build-skills: - title: "pyOpenSci volunteers build skills and community" excerpt: "When you volunteer with pyOpenSci, you’re both giving back and developing professional skills. As a volunteer you will: @@ -34,16 +34,16 @@ help-us: btn_label: "> Check out our GitHub Help Wanted Board" btn_class: btn--inverse - image_path: - title: "Sign up to be a scientific Python package reviewer" + title: "Sign up to review a Python package" alt: - excerpt: "Finding reviewers is one of the more challenging parts of running a peer review process. We are always looking for new reviewers from a broad range of scientific domains. Some reviewers have extensive packaging expertise and others have domain expertise. We think that mix is great, so sign up today! If you are new to reviewing we are happy to support you through our peer review mentorship program." + excerpt: "We are always looking for new reviewers from a broad range of scientific domains. Some reviewers have extensive packaging expertise and others have domain expertise or focus on package usability. If you are new to reviewing we are happy to support you through our peer review mentorship program. [Learn more about the reviewer role](https://www.pyopensci.org/software-peer-review/how-to/reviewer-guide.html) and sign up using the link below." url: https://forms.gle/GHfxvmS47nQFDcBM6 btn_label: "> Sign up now." btn_class: btn--inverse - image_path: title: "Get involved as software peer review Editor" alt: - excerpt: "We also often recruit new editors to support our peer review process. Keep an eye out on our [Discourse forum](https://pyopensci.discourse.group/) for calls for new editors. In the meantime if you are interested in learning more about the editor role, check out our [peer review guidebook](https://www.pyopensci.org/software-peer-review/). " + excerpt: "We also often recruit new editors to support our peer review process. " url: https://www.pyopensci.org/software-peer-review/how-to/editors-guide.html btn_label: "> Click here to learn more about the editor role." btn_class: btn--inverse @@ -51,26 +51,6 @@ help-us: {% include feature_row id="volunteer-mission" type="center" %} -
-
- -{% include feature_row id="diverse-backgrounds" type="left" %} - -
-
- -{% include div_purple_bottom.html %} - - -
-
- -{% include feature_row id="build-skills" type="right" %} - -
-
- -{% include div_purple_top.html %}
@@ -101,6 +81,26 @@ And last but not least, we’d also love for you to be a guest blogger on the [p
+
+
+ +{% include feature_row id="diverse-backgrounds" type="left" %} + +
+
+ +{% include div_purple_bottom.html %} + + +
+
+ +{% include feature_row id="build-skills" type="right" %} + +
+
+ +{% include div_purple_top.html %} {% include div_purple_top.html %} diff --git a/_posts/2025-03-13-python-packaging-security-pypi.md b/_posts/2025-03-13-python-packaging-security-pypi.md new file mode 100644 index 00000000..e37dfe66 --- /dev/null +++ b/_posts/2025-03-13-python-packaging-security-pypi.md @@ -0,0 +1,324 @@ +--- +layout: single +title: "How to Secure Your Python Packages When Publishing to PyPI" +excerpt: "Learn how to secure your Python package PyPI publishing workflows and protect your package from attacks. This post covers actionable steps, using PyPI Trusted Publisher, and sanitizing workflows, to ensure your projects stay safe." +author: "Leah Wasser" +permalink: /blog/python-packaging-security-publish-pypi.html +header: + overlay_image: images/headers/pyopensci-inessa.png + overlay_filter: rgba(20, 13, 36, 0.3) +categories: + - python-packaging + - blog-post + - community +classes: wide +toc: true +comments: true +last_modified: 2025-03-13 +--- + +## Is your PyPI publication workflow secure? + +We can learn a lot from the Python package breach [involving Ultralytics](https://blog.pypi.org/posts/2024-12-11-ultralytics-attack-analysis/). This breach highlighted the importance of making our PyPI publishing workflows for Python packages more secure. + +In this breach, hackers exploited a GitHub Actions workflow to inject malicious code into a Python package. This package was then published to PyPI. The outcome: users who downloaded the package unknowingly allowed their machines to be hijacked for Bitcoin mining. + +{% include pyos-blockquote.html quote="Hackers tricked a Python package into running bad code, using other people’s computers to mine Bitcoin without permission. Yikes!" class="highlight" %} + +While unsettling, there’s a silver lining: the PyPI security team had already addressed most of the issues that caused this breach. + +{% include pyos-blockquote.html quote="Because the Ultralytics project was using Trusted Publishing and the PyPA’s publishing GitHub Action: PyPI staff, volunteers, and security researchers were able to dig into how maliciously injected software was able to make its way into the package." author="Seth Larson, PSF Security Expert" class="highlight magenta" %} + +This means that the important thing for us, as maintainers, is that we all should know how to lock down our publishing workflows. +Here, I'll cover the lessons learned that you can apply TODAY to your Python packaging workflows! + +*Special thanks to [Seth Larson](https://github.com/sethmlarson), [Hugo van Kemenade](https://github.com/hugovk), [Sviatoslav Sydorenko](https://github.com/webknjaz), [William Woodruff](https://github.com/woodruffw) and [Carol Willing](https://github.com/willingc) for reviewing and significantly improving blog post!!* + +
+## TL;DR Takeaways + +The fall 2024 Ultralytics breach was a wake-up call for all maintainers: secure your workflows to protect your users and the Python ecosystem. The most important steps that you can take are actually the simplest: + +Below are **3 things that you can do right now** to secure your PyPI Python packaging workflow: + +### Secure GitHub--Human and GitHub--PyPI connections + +1. 🔒 If you have a GitHub Action that publishes to PyPI, make sure that the **publish section of your action uses a controlled GitHub environment**. Name that environment `pypi` and set environment permissions in GitHub that allow specific trusted maintainers to authorize the environment to run. I'll show you how to do this below. +1. 🤝 Create a **Trusted Publisher link between your package's (GitHub/GitLab) repository and PyPI**. You can call this trusted connection within the locked-down GitHub environment (named `pypi`) that you created above. +1. 🍒 Add [`zizmor`](https://woodruffw.github.io/zizmor/) to your build to check GitHub Actions for vulnerabilities. You can run zizmor on your workflow files locally, or you can set it up as a pre-commit hook which is probably a better bet. + +Together, these three steps protect both sides of your PyPI publication process--the trigger on GitHub and the connection between GitHub and PyPI. 🚀🚀🚀 + +Don’t wait--start securing your Python publishing workflows today. 🔒 + +
+ + +## A call to (GitHub) actions ... + +The Ultralytics breach highlights the need for us all to follow and understand secure PyPI publishing practices and carefully monitor workflows. Below are actionable steps you can take to enhance security when publishing Python packages to PyPI using GitHub Actions. + + [PyPA provides a great overview of using actions to publish your Python package.](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/) +{: .notice } + +## 1. Create a dedicated GitHub environment for publishing actions + +First, make sure that your PyPI publish GitHub Action uses an isolated GitHub environment. Isolated environments ensure your publishing process remains secure even if other parts of your CI pipeline are compromised. This is because you can lock an environment down by ensuring that only specific users can authorize this environment to run. + + +A GitHub Action is a CI/CD (Continuous Integration/Continuous Deployment) tool that allows you to automate tests. [Click here to read more about what CI/CI is.](https://www.pyopensci.org/python-package-guide/continuous-integration/ci.html) +{: .notice .notice--success } + +If you look at the workflow example below, notice that we have an [environment called `pypi`](https://github.com/pyOpenSci/pyosMeta/blob/2a09fba/.github/workflows/publish-pypi.yml#L57) that is used for trusted publishing. The `pypi` environment creates a direct link between this action and PyPI Trusted Published (discussed below). + +```yaml + publish: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: github.repository_owner == 'pyopensci' + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/pyosmeta +``` +***** + +To lock down a GitHub environment: + +* First, go to the Settings in your repository where the workflow is run +* Within settings, select **environments** from the left-hand sidebar +* Add a new environment. Use pypi as your environment name; this is what PyPA (the Python Packaging Authority) recommends. +* Ensure Required reviewers is enabled. This setting allows you to designate specific individuals who can approve and manually run the workflow on GitHub. Any reviewers you add must have the appropriate permissions to authorize the workflow by clicking a button. This adds a human verification step to the process. +* Once the Required reviewers button is checked, add maintainers who you want to be able to enable the action to run. + +*Optionally, you can click prevent self-review, preventing someone from triggering a release or a build and then running it!* + +
+ Animated gif file that shows the GitHub interface where you can click on settings and go to the environment setting to create or edit a GitHub environment +
+ To create a new environment to use in a GitHub Action, 1) go to your repo's settings; 2) click environment; 3) add a new environment. In this screenshot, we already have a pypi environment created. Note that you can name your environment whatever you want, however, PyPI suggests that you use the name pypi for a Trusted Publisher workflow. +
+
+ + +
+ + Screenshot of the GitHub settings interface showing the ‘Environments’ section with configuration options for ‘pypi.’ The ‘Deployment protection rules’ section is visible, with ‘Required reviewers’ enabled and two reviewers listed: ‘lwasser’ and ‘willingc.’ Other options such as ‘Prevent self-review’ and ‘Wait timer’ are present but not enabled. +
+ GitHub environment settings for “pypi,” displaying deployment protection rules with required reviewers configured for workflow approvals. +
+
+ + +## 2. 🔑 Use Trusted Publisher for PyPI + +Now that you have a GitHub environment setup, you can set up Trusted Publisher in your PyPI account. + +A Trusted Publisher setup creates a secure link between PyPI and your repository. +- PyPI is allowed to authenticate your [package distribution files (sdist and wheel archives)](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-distribution-files-sdist-wheel.html#how-to-create-the-distribution-format-that-pypi-and-pip-expects) uploads directly, so no additional configuration is required. +- Trusted Publisher restricts publishing to a specific GitHub Actions workflows and environments defined in your repository. + +Using a Trusted Publisher combined with a locked-down environment eliminates the need to store sensitive tokens as GitHub secrets. It also removes the need to refresh and update tokens periodically to avoid token leaks or theft issues. + +
+ + A workflow diagram showing GitHub Actions building distribution files (sdist and wheel), publishing them securely to PyPI, represented as a warehouse. The diagram includes a lock icon emphasizing security, with the pyOpenSci logo in the top-left corner. +
+ Example of the PyPI Trusted Publisher form, used to securely link a GitHub repository with PyPI for publishing Python packages. Trusted Publisher reduces the risk of token theft and improves overall security. +
+
+ +If you only [publish locally to PyPI using the command line](https://www.pyopensci.org/python-package-guide/tutorials/publish-pypi.html), you must use a PyPI token. However, if you’re using GitHub Actions to automate your publishing process, setting up **Trusted Publisher** is a secure and easier-to-manage option. +{: .notice } + +### How to get started + +[PyPI provides a great guide to getting started with Trusted Publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/). + + +The steps for setting up Trusted Publisher are: +1. Login to your PyPI account +2. Click on your profile to take you to **Your projects**. +3. Click on **publishing** on the left-hand side of the site. (it's below account settings). +4. At the top of the page is a Manage Publishers section. At the bottom, you will see **Add a new pending publisher** +5. Fill out a form that looks like the one below in the add a new pending publisher section. Notice that you can select GitHub, GitLab, Google and ActiveState as platforms. +6. Notice that the form asks for your project name, owner, repo name, workflow's file name, and environment (**STRONGLY recommended**). + + +
+ + + PyPI Trusted Publisher form example showing settings for linking a GitHub repository with PyPI for secure publishing. + +
+ Example of the PyPI Trusted Publisher form, used to securely link a GitHub repository with PyPI for publishing Python packages. Trusted Publisher reduces the risk of token theft and improves overall security. +
+
+ +For an example of a GitHub workflow that uses Trusted Publishing, check out our active pyOpenSci [PyPI publishing GitHub workflow](https://github.com/pyOpenSci/pyosMeta/blob/main/.github/workflows/publish-pypi.yml), which follows the Trusted Publisher approach. + + +
+ + + PyPI Trusted Publisher manage settings showing what the Trusted Publisher setup looks like after you've created it in PyPI. It shows all of the items that you filled out in the form and has a remove button if you want to remove it from PyPI. + +
+ Example of the PyPI Trusted Publisher setup in PyPI once you've created the Trusted PuUblisher link by filling the form out above. +
+
+ + +**Note:** Read more here about [support for publishing to GitLab](https://docs.pypi.org/trusted-publishers/adding-a-publisher/#gitlab-cicd) using trusted publishing. +{: .notice } + +## 3. Add `zizmor` to your CI workflows + +Finally, consider adding [Zizmor](https://woodruffw.github.io/zizmor/) to your CI and pre-commit checks. + +Zizmor is a static analysis tool designed to help identify GitHub Action security issues. Zizmor scans your workflows and highlights common vulnerabilities, ensuring your (continuous integration / continuous deployment) pipelines remain secure and efficient. + +**TODO: link to packaging guide page on CI when it's published friday** + +Named as a playful nod to Dr. Zizmor’s famous “clear skin” ads, zizmor aims to give you “beautiful clean workflows.” + +Learn more about zizmor on the [official blog post by William Woodruff](https://blog.yossarian.net/2024/10/27/Now-you-can-have-beautiful-clean-workflows). +{: .notice .notice--success } + +### How it works + +To use zizmor locally to check your workflows, first install it using `pip` or `pipx`: + +`pip install zizmor` + +Then, ask it to check a specific workflow file (or set of files). + +Below, I ran it on our pyosMeta PyPI build. Among other things, it found a template injection risk in our build that we can easily fix by adding a sanitization step discussed below! + +PyPI really is on top of things! + +```console +$ zizmor .github/workflows/publish-pypi.yml + +error[template-injection]: code injection via template expansion + --> path/here/pyosMeta/.github/workflows/publish-pypi.yml:97:7 +github.ref_name may expand into attacker-controllable code +``` + +You can also set up `zizmor` as a pre-commit hook. pyOpenSci plans to do this in the future, but here is an example of it [set up for core Python](https://github.com/python/cpython/pull/127749/files#diff-63a9c44a44acf85fea213a857769990937107cf072831e1a26808cfde9d096b9R64). + +Pre-commit hooks run checks every time you commit a file to Git history. [Learn more about using them here.](https://www.pyopensci.org/python-package-guide/package-structure-code/code-style-linting-format.html#use-pre-commit-hooks-to-run-code-formatters-and-linters-on-commits) + +## Other security measures you can consider + +There are other things that we can learn too from the recent breach. Many of these will be identified if you set up zizmor. These are discussed below. + + +### Sanitize branch names in your workflow + +One of the critical issues in the Ultralytics breach involved a +[**malicious branch name containing a shell script**](https://github.com/ultralytics/ultralytics/pull/18020) +that was executed because `github.ref` was used without sanitization. This type +of attack, known as **template injection**, allows malicious content in branch +names to be treated as shell commands. + +{% include pyos-blockquote.html quote="...is a classic GitHub Actions template injection: the expansion of `github.head_ref || github.ref` is injected directly into the shell’s context, with no quoting or interpolation.." author="https://blog.yossarian.net/2024/12/06/zizmor-ultralytics-injection" class="highlight magenta" %} + +Because the branch name wasn’t sanitized, it was treated as a shell command and executed with full permissions. Yikes! + +In the example below, an unsanitized branch name could execute harmful commands: + +```yaml +jobs: + example-job: + runs-on: ubuntu-latest + steps: + - name: Run a script + run: | + echo "Running script for branch: $GITHUB_REF" +``` + +To prevent this, [sanitize or clean](https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names) branch names before using them. A small Bash step can +remove unsafe characters: + + +```yaml +jobs: + example-job: + runs-on: ubuntu-latest + steps: + - name: Sanitize branch name + run: | + SAFE_BRANCH=$(echo $GITHUB_REF | sed 's/[^a-zA-Z0-9_\-\/]//g') + echo "Sanitized branch name: $SAFE_BRANCH" + echo "Running script for branch: $SAFE_BRANCH" +``` + + +## Lock down GitHub permissions & delete old PyPI tokens and GitHub secrets + +In addition to securing your workflows, lock down your accounts and repositories. 2FA (2-factor authentication) is thankfully now required as a security measure for both GitHub and PyPI. However, be sure to store your recovery codes somewhere safe (like in a password manager!). + +Also consider: + +- **Revoking old tokens**: If you've previously created PyPI API tokens and/or associated GitHub secrets, delete any unused or outdated ones. +- **Restrict repository access**: Limit write GitHub repository access to a trusted subset of maintainers. Most contributors don’t need direct write access, which reduces security risks. + + +### 🚫 Avoid `pull_request_target` and consider release-based workflows + +A trigger event in a GitHub Action is an event that sets off an action to run. For instance, you might have a trigger that runs a linter like Black or Ruff when a new pull request is opened. + +The [`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) trigger event in GitHub Actions that Ultralytics used allows workflows to run with elevated permissions on the base branch, even when triggered by changes from a fork. Thus, your workflow becomes vulnerable when used as a trigger to push a release to PyPI. + +Instead of a pull_request_target or a pull_request, consider adopting a **release-based publishing workflow**. This approach: + +- Triggers publication workflows only on new versioned releases. You can lock down which maintainers are allowed to create releases using GitHub permissions +- Ensure workflows related to publishing are explicitly scoped to `release` events. + +In the example GitHub Action `.yaml` file below, you see a `release` trigger defined. This tells the action to only trigger the workflow when you publish a release. + + +```yaml +name: Publish to PyPI +on: + # By using release as a trigger, only GitHub users and actions with write access to make releases to our repo can trigger the push to PyPI + release: + types: [published] +``` + +Using a release-based workflow ensures that your publishing step is tightly controlled. A pull request will never accidentally trigger a publish build. This reduces your risk! + + + +## Don’t cache package dependencies in your publish step + +Caching dependencies involves storing dependencies to be reused in future workflow runs. This approach saves time, as GitHub doesn’t need to redownload all dependencies each time the workflow runs. + +However, caching dependencies can allow attackers to manipulate cached artifacts. If this happens, the workflow may pull in compromised versions from the cache during the next run. + + +## Learn More + +pyOpenSci follows best practices for PyPI publishing using our custom GitHub Actions workflow. Check out our tutorial on Python packaging here: +👉 [pyOpenSci Packaging Tutorial](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html) +👉 Join our discourse here + +
+## Get involved with pyOpenSci + +* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) if you are interested in getting involved. +* Keep an eye on our [events page](/events.html) for upcoming training events. + +Follow us on social platforms: + +* [ Discourse](https://pyopensci.discourse.group/) +* [ Mastodon](https://fosstodon.org/@pyopensci) +* [ Bluesky](https://bsky.app/profile/pyopensci.org) +* [ LinkedIn](https://www.linkedin.com/company/pyopensci) +* [ GitHub](https://github.com/pyOpenSci) + +If you are on LinkedIn, you should [subscribe to our newsletter, too](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true). +
diff --git a/_sass/minimal-mistakes/_base.scss b/_sass/minimal-mistakes/_base.scss index fd709273..45b0beeb 100644 --- a/_sass/minimal-mistakes/_base.scss +++ b/_sass/minimal-mistakes/_base.scss @@ -262,6 +262,24 @@ pre { font-size: 1em; } + + + +kbd { + background-color: $kbd-color-background; + color: $kbd-color-text; + border-radius: 0.25rem; + box-shadow: 0 2px 0 1px $kbd-color-border; + line-height: 1; + font-size: .75em; + padding: .15em .25em; + + &:hover { + box-shadow: 0 1px 0 0.5px $kbd-color-border; + top: 1px; + } +} + pre { overflow-x: auto; /* add scrollbars to wide code blocks*/ } diff --git a/_sass/minimal-mistakes/_variables.scss b/_sass/minimal-mistakes/_variables.scss index 66204dbf..9364e3bf 100644 --- a/_sass/minimal-mistakes/_variables.scss +++ b/_sass/minimal-mistakes/_variables.scss @@ -70,6 +70,11 @@ $h-size-4: 1.0625em !default; // ~17px $h-size-5: 1.03125em !default; // ~16.5px $h-size-6: 1em !default; // ~16px +/* kbd colors */ +$kbd-color-background: #bbbdc3; +$kbd-color-border: #999ba5; +$kbd-color-text: #222325; + /* Colors ========================================================================== */ diff --git a/images/python-packaging/create-github-environment.gif b/images/python-packaging/create-github-environment.gif new file mode 100644 index 00000000..c279ed96 Binary files /dev/null and b/images/python-packaging/create-github-environment.gif differ diff --git a/images/python-packaging/github-action-environment-pypi.png b/images/python-packaging/github-action-environment-pypi.png new file mode 100644 index 00000000..822ba25a Binary files /dev/null and b/images/python-packaging/github-action-environment-pypi.png differ diff --git a/images/python-packaging/github-action-environment-pypi.webp b/images/python-packaging/github-action-environment-pypi.webp new file mode 100644 index 00000000..ede3925d Binary files /dev/null and b/images/python-packaging/github-action-environment-pypi.webp differ diff --git a/images/python-packaging/trusted-publisher-form.png b/images/python-packaging/trusted-publisher-form.png new file mode 100644 index 00000000..ffbc33b7 Binary files /dev/null and b/images/python-packaging/trusted-publisher-form.png differ diff --git a/images/python-packaging/trusted-publisher-form.webp b/images/python-packaging/trusted-publisher-form.webp new file mode 100644 index 00000000..de45575c Binary files /dev/null and b/images/python-packaging/trusted-publisher-form.webp differ diff --git a/images/python-packaging/trusted-publisher-manage.png b/images/python-packaging/trusted-publisher-manage.png new file mode 100644 index 00000000..6b8c5d5d Binary files /dev/null and b/images/python-packaging/trusted-publisher-manage.png differ diff --git a/images/python-packaging/trusted-publisher-manage.webp b/images/python-packaging/trusted-publisher-manage.webp new file mode 100644 index 00000000..c228afec Binary files /dev/null and b/images/python-packaging/trusted-publisher-manage.webp differ diff --git a/images/python-packaging/trusted-publisher-pypi-github.png b/images/python-packaging/trusted-publisher-pypi-github.png new file mode 100644 index 00000000..b2c5eb11 Binary files /dev/null and b/images/python-packaging/trusted-publisher-pypi-github.png differ diff --git a/images/python-packaging/trusted-publisher-pypi-github.webp b/images/python-packaging/trusted-publisher-pypi-github.webp new file mode 100644 index 00000000..fab5edc7 Binary files /dev/null and b/images/python-packaging/trusted-publisher-pypi-github.webp differ