1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Enable Bring-Your-Own-Secrets (#982)

* Move env from steps to job
* Rename env to be predictable.
* fix conditional
* document B.O.Y.S.
* Update docs/byo-secrets.md

Co-authored-by: Max Horstmann <MaxHorstmann@users.noreply.github.com>

* respond to reviews
* fixup!

Co-authored-by: Max Horstmann <MaxHorstmann@users.noreply.github.com>
This commit is contained in:
Tom Limoncelli
2020-11-29 14:15:40 -05:00
committed by GitHub
parent f81260ba71
commit 12a3354c0c
4 changed files with 274 additions and 78 deletions

View File

@ -1,3 +1,4 @@
---
name: build
on:
@ -44,6 +45,69 @@ jobs:
- DIGITALOCEAN
- GANDI_V5
- INWX
# Bring-Your-Own-Secrets:
# To reduce the risk of secrets being logged by third-parties, secrets
# come from the account of the fork. For example, the PR submitted by
# a member of the project has access to the secrets in
# github.com/StackExchange/dnscontrol. However a PR submitted by a
# third-party receives secrets from the account of their fork.
#
# If a test requires no secrets: List any parameters here in
# plaintext. (see BIND and HEXONET as examples).
# However secrets are needed for most tests. In that case, create a secret called
# ${PROVIDER}_DOMAIN and other env variables listed in
# integrationTest/providers.json for that provider. the test will only run on systems
# with access to those secrets (specifically, the ${PROVIDER}_DOMAIN secret).
# This way the main project can maintain its tests and secrets
# securely, plus forks can run their own tests.
#
# See https://stackexchange.github.io/dnscontrol/byo-secrets
#
# (Sort order: groups in the same order as the matrix; _DOMAIN first; sort the others alphabetically.)
env:
BIND_DOMAIN: example.com
#
HEXONET_DOMAIN: a-b-c-movies.com
HEXONET_ENTITY: OTE
HEXONET_PW: test.passw0rd
HEXONET_UID: test.user
#
AZURE_DNS_DOMAIN: ${{ secrets.AZURE_DNS_DOMAIN }}
AZURE_DNS_CLIENT_ID: ${{ secrets.AZURE_DNS_CLIENT_ID }}
AZURE_DNS_CLIENT_SECRET: ${{ secrets.AZURE_DNS_CLIENT_SECRET }}
AZURE_DNS_RESOURCE_GROUP: DNSControl
AZURE_DNS_SUBSCRIPTION_ID: ${{ secrets.AZURE_DNS_SUBSCRIPTION_ID }}
AZURE_DNS_TENANT_ID: ${{ secrets.AZURE_DNS_TENANT_ID }}
#
CLOUDFLAREAPI_DOMAIN: ${{ secrets.CLOUDFLAREAPI_DOMAIN }}
CLOUDFLAREAPI_KEY: ${{ secrets.CLOUDFLAREAPI_KEY }}
CLOUDFLAREAPI_TOKEN: ${{ secrets.CLOUDFLAREAPI_TOKEN }}
CLOUDFLAREAPI_USER: ${{ secrets.CLOUDFLAREAPI_USER }}
#
GCLOUD_DOMAIN: ${{ secrets.GCLOUD_DOMAIN }}
GCLOUD_EMAIL: dnscontrol@dnscontrol-dev.iam.gserviceaccount.com
GCLOUD_PRIVATEKEY: ${{ secrets.GCLOUD_PRIVATEKEY }}
GCLOUD_PROJECT: dnscontrol-dev
GCLOUD_TYPE: service_account
#
NAMEDOTCOM_DOMAIN: ${{ secrets.NAMEDOTCOM_DOMAIN }}
NAMEDOTCOM_KEY: ${{ secrets.NAMEDOTCOM_KEY }}
NAMEDOTCOM_URL: api.name.com
NAMEDOTCOM_USER: dnscontroltest
#
ROUTE53_DOMAIN: ${{ secrets.ROUTE53_DOMAIN }}
ROUTE53_KEY: ${{ secrets.ROUTE53_KEY }}
ROUTE53_KEY_ID: ${{ secrets.ROUTE53_KEY_ID }}
#
DIGITALOCEAN_DOMAIN: ${{ secrets.DIGITALOCEAN_DOMAIN }}
DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
#
GANDI_V5_DOMAIN: ${{ secrets.GANDI_V5_DOMAIN }}
GANDI_V5_APIKEY: ${{ secrets.GANDI_V5_APIKEY }}
#
INWX_DOMAIN: ${{ secrets.INWX_DOMAIN }}
INWX_PASSWORD: ${{ secrets.INWX_PASSWORD }}
INWX_USER: ${{ secrets.INWX_USER }}
steps:
- name: Checkout repo
uses: actions/checkout@v2
@ -54,60 +118,9 @@ jobs:
with:
go-version: ^1.15
- name: Determining test viability for ${{ matrix.provider }} provider
run: if [ "$${{ matrix.provider }}__CAN_SECRET" = "true" ] ; then echo "CAN_CONTINUE=yes" >> "$GITHUB_ENV" ; fi
# Does the provider's tests require secrets?
# Yes? Set a secret called ${PROVIDER}__CAN_SECRET with value "true" (no quotes).
# No? Set it to "true" like you see for BIND__CAN_SECRET.
# This way tests only run if the secrets are available to the runner.
# A fork can "bring your own secrets" for locally-defined tests.
# Please keep the list sorted.
env:
AZURE_DNS__CAN_SECRET: ${{ secrets.AZURE_DNS__CAN_SECRET }}
BIND__CAN_SECRET: true
CLOUDFLAREAPI__CAN_SECRET: ${{ secrets.CLOUDFLAREAPI__CAN_SECRET }}
DIGITALOCEAN__CAN_SECRET: ${{ secrets.DIGITALOCEAN__CAN_SECRET }}
GANDI_V5__CAN_SECRET: ${{ secrets.GANDI_V5__CAN_SECRET }}
GCLOUD__CAN_SECRET: ${{ secrets.GCLOUD__CAN_SECRET }}
HEXONET__CAN_SECRET: true
INWX__CAN_SECRET: ${{ secrets.INWX__CAN_SECRET }}
NAMEDOTCOM__CAN_SECRET: ${{ secrets.NAMEDOTCOM__CAN_SECRET }}
ROUTE53__CAN_SECRET: ${{ secrets.ROUTE53__CAN_SECRET }}
run: if [ -n "$${{ matrix.provider }}_DOMAIN" ] ; then echo "CAN_CONTINUE=yes" >> "$GITHUB_ENV" ; fi
- name: Run integration tests for ${{ matrix.provider }} provider
if: env.CAN_CONTINUE == 'yes'
working-directory: integrationTest
run: go test -v -verbose -provider ${{ matrix.provider }}
if: ${{ env.CAN_CONTINUE == 'yes' }}
# Extract the secrets that are used by the tests. (Please keep this list sorted)
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_DOMAIN: dnscontrol-azure.com
AZURE_RESOURCE_GROUP: DNSControl
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
CF_DOMAIN: ${{ secrets.CF_DOMAIN }}
CF_KEY: ${{ secrets.CF_KEY }}
CF_TOKEN: ${{ secrets.CF_TOKEN }}
CF_USER: ${{ secrets.CF_USER }}
DO_DOMAIN: dnscontrol-do.com
DO_TOKEN: ${{ secrets.DO_TOKEN }}
GANDI_V5_APIKEY: ${{ secrets.GANDI_V5_APIKEY }}
GANDI_V5_DOMAIN: dnscontroltest-gandilivedns.com
GCLOUD_DOMAIN: dnscontroltest-gcloud.com
GCLOUD_EMAIL: dnscontrol@dnscontrol-dev.iam.gserviceaccount.com
GCLOUD_PRIVATEKEY: ${{ secrets.GCLOUD_PRIVATEKEY }}
GCLOUD_PROJECT: dnscontrol-dev
GCLOUD_TYPE: service_account
HEXONET_DOMAIN: a-b-c-movies.com
HEXONET_ENTITY: OTE
HEXONET_PW: test.passw0rd
HEXONET_UID: test.user
INWX_DOMAIN: ${{ secrets.INWX_DOMAIN }}
INWX_PASSWORD: ${{ secrets.INWX_PASSWORD }}
INWX_USER: ${{ secrets.INWX_USER }}
NAMEDOTCOM_DOMAIN: dnscontrol-ndc.com
NAMEDOTCOM_KEY: ${{ secrets.NAMEDOTCOM_KEY }}
NAMEDOTCOM_URL: api.name.com
NAMEDOTCOM_USER: dnscontroltest
R53_DOMAIN: dnscontroltest-r53.com
R53_KEY: ${{ secrets.R53_KEY }}
R53_KEY_ID: ${{ secrets.R53_KEY_ID }}
...

180
docs/byo-secrets.md Normal file
View File

@ -0,0 +1,180 @@
---
layout: default
title: Bring-Your-Own-Secrets for automated testing
---
# Bring-Your-Own-Secrets for automated testing
Goal: Enable automated integration testing without accidentally
leaking our API keys and other secrets; at the same time permit anyone
to automate their own tests without having to share their API keys and
secrets.
* PR from a project member:
* Automated tests run for a long list of providers. All officially supported
providers have automated tests, plus a few others too.
* PR from an external person
* Automated tests run for a short list of providers. Any test that
requires secrets are skipped in the fork. They will run after the fact though
once the PR has been merged to into the `master` branch of StackExchange/dnscontrol.
* PR from an external person that wants automated tests for their
provider.
* They can set up secrets in their own GitHub account for any tests
they'd like to automate without sharing their secrets.
* Note: These tests can always be run outside of GitHub at the
command line.
# Background: How GitHub Actions protects secrets
Github Actions has a secure
[secrets storage system](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets).
Those secrets are available to Github Actions and are required for the
integration tests to communicate with the various DNS providers that
DNSControl supports.
For security reasons, those secrets are unavailable if the PR comes
from outside the project (a forked repo). This is a good thing. If
it didn't work that way, a third-party could write a PR that leaks the
secrets without the owners of the project knowing.
The docs (and many blog posts) describe this as forked repos don't
have access to secrets, and instead receive null strings. That's not
actually what's happening.
Actually what happens is the secrets come from the forked repo. Or,
more precisely, the secrets offered to a PR come from the repo that the
PR came from. A PR from DNSControl's owners gets secrets from
[github.com/StackExchange/dnscontrol's secret store](https://github.com/StackExchange/dnscontrol/settings/secrets/actions)
but a PR from a fork, such as
[https://github.com/TomOnTime/dnscontrol](https://github.com/TomOnTime/dnscontrol)
gets its secrets from TomOnTime's secrets.
Our automated integration tests leverages this info to have tests
only run if they have access to the secrets they will need.
# How it works:
Tests are executed if `*_DOMAIN` exists. If the value is empty or
unset, the test is skipped. If a test doesn't require secrets, the
`*_DOMAIN` variable is hardcoded. Otherwise, it is set by looking up
the secret. For example, if a provider is called `FANCYDNS`, there must
be a secret called `FANCYDNS_DOMAIN`.
# Bring your own secrets
This section describes how to add a provider to the testing system.
In this example, we will use a fictional DNS provider named
"FANCYDNS".
Step 1: Create a branch
Create a branch as you normally would to submit a PR to the project.
Step 2: Update `build.yml`
In this branch, edit `.github/workflows/build.yml`:
1. In the `integration-tests` section, add the name of your provider
to the matrix of providers. Technically you are adding to the list
at `jobs.integration-tests.strategy.matrix.provider`.
```
matrix:
provider:
...
- DIGITALOCEAN
- GANDI_V5
- FANCYDNS <<< NEW ITEM ADDED HERE
- INWX
```
2. Add your test's env:
Locate the env section (technically this is `jobs.integration-tests.env`) and
add all the env names that your provider sets in
`integrationTest/providers.json`.
Please replicate the formatting of the existing entries:
* A blank comment separates each provider's section.
* The providers are listed in the same order as the matrix.provider list.
* The `*_DOMAIN` variable is first.
* The remaining variables are sorted lexicographically (what nerds call alphabetical order).
```
FANCYDNS_DOMAIN: ${{ secrets.FANCYDNS_DOMAIN }}
FANCYDNS_KEY: ${{ secrets.FANCYDNS_KEY }}
FANCYDNS_USER: ${{ secrets.FANCYDNS_USER }}
```
# Examples
Let's look at three examples:
## Example 1:
The `BIND` integration tests do not require any secrets because it
simply generates files locally.
```
BIND_DOMAIN: example.com
```
The existence of `BIND_DOMAIN`, and the fact that the value is
available to all, means these tests will run for everyone.
## Example 2:
The `AZURE_DNS` provider requires many settings. Since
`AZURE_DNS_DOMAIN` comes from GHA's secrets storage, we can be assured
that the tests will skip if the PR does not have access to the
secrets.
If you have a fork and want to automate the testing of `AZURE_DNS`,
simply set the secrets named in `build.yml` and the tests will
activate for your PRs.
Note that `AZURE_DNS_RESOURCE_GROUP` is hardcoded to `DNSControl`. If
this is not true for you, please feel free to submit a PR that turns
it into a secret.
```
AZURE_DNS_DOMAIN: ${{ secrets.AZURE_DNS_DOMAIN }}
AZURE_DNS_CLIENT_ID: ${{ secrets.AZURE_DNS_CLIENT_ID }}
AZURE_DNS_CLIENT_SECRET: ${{ secrets.AZURE_DNS_CLIENT_SECRET }}
AZURE_DNS_RESOURCE_GROUP: DNSControl
AZURE_DNS_SUBSCRIPTION_ID: ${{ secrets.AZURE_DNS_SUBSCRIPTION_ID }}
AZURE_DNS_TENANT_ID: ${{ secrets.AZURE_DNS_TENANT_ID }}
```
## Example 3:
The HEXONET integration tests require secrets, but HEXONET provides an
Operational Test and Evaluation (OT&E) environment with some "fake"
credentials which are known publicly.
Therefore, since there's nothing secret about these particular
secrets, we hard-code them into the `build.yml` file. Since
`HEXONET_DOMAIN` does not come from secret storage, everyone can run
these tests. (We are grateful to HEXONET for this public service!)
```
HEXONET_DOMAIN: a-b-c-movies.com
HEXONET_ENTITY: OTE
HEXONET_PW: test.passw0rd
HEXONET_UID: test.user
```
NOTE: The above credentials are [known to the public]({{site.github.url}}/providers/hexonet).
# Caveats
Sadly there is no locking to prevent two PRs from running the same
test on the same domain at the same time. When that happens, both PRs
running the tests fail. In the future we hope to add some locking.
Also, maintaining a fork requires keeping it up to date. That's a bit
more Git knowledge than I can describe here. (I'm not a Git expert by
any stretch of the imagination!)

View File

@ -43,7 +43,7 @@ title: DNSControl
We make it easy to contribute by using
<strong><a href="https://github.com/StackExchange/dnscontrol">GitHub</a></strong>,
you can make code changes with confidence thanks to extensive integration tests.
The project is
The project is
<strong><a href="https://everythingsysadmin.com/2017/08/go-get-up-to-speed.html">newbie-friendly</a></strong>
so jump right in!
</p>
@ -164,16 +164,19 @@ title: DNSControl
Mailing list: <a href="https://groups.google.com/forum/#!forum/dnscontrol-discuss">dnscontrol-discuss</a>: The friendly best place to ask questions and propose new features
</li>
<li>
<a href="{{site.github.url}}/writing-providers">Step-by-Step Guide: Writing Providers</a>: How to write a DNS or Registrar Provider
</li>
<li>
<a href="{{site.github.url}}/adding-new-rtypes">Step-by-Step Guide: Adding new DNS rtypes</a>: How to add a new DNS record type
<a href="{{site.github.url}}/bug-triage">Bug Triage</a>: How bugs are triaged
</li>
<li>
<a href="{{site.github.url}}/release-engineering">Release Engineering</a>: How to build and ship a release
</li>
<li>
<a href="{{site.github.url}}/bug-triage">Bug Triage</a>: How bugs are triaged
<a href="{{site.github.url}}/byo-secrets">Bring-Your-Own-Secrets</a>: Automate tests
</li>
<li>
<a href="{{site.github.url}}/writing-providers">Step-by-Step Guide: Writing Providers</a>: How to write a DNS or Registrar Provider
</li>
<li>
<a href="{{site.github.url}}/adding-new-rtypes">Step-by-Step Guide: Adding new DNS rtypes</a>: How to add a new DNS record type
</li>
</ul>
</div>

View File

@ -11,21 +11,21 @@
"update-key": "$AXFRDDNS_UPDATE_KEY"
},
"AZURE_DNS": {
"ClientID": "$AZURE_CLIENT_ID",
"ClientSecret": "$AZURE_CLIENT_SECRET",
"ResourceGroup": "$AZURE_RESOURCE_GROUP",
"SubscriptionID": "$AZURE_SUBSCRIPTION_ID",
"TenantID": "$AZURE_TENANT_ID",
"domain": "$AZURE_DOMAIN"
"ClientID": "$AZURE_DNS_CLIENT_ID",
"ClientSecret": "$AZURE_DNS_CLIENT_SECRET",
"ResourceGroup": "$AZURE_DNS_RESOURCE_GROUP",
"SubscriptionID": "$AZURE_DNS_SUBSCRIPTION_ID",
"TenantID": "$AZURE_DNS_TENANT_ID",
"domain": "$AZURE_DNS_DOMAIN"
},
"BIND": {
"domain": "example.com"
"domain": "$BIND_DOMAIN"
},
"CLOUDFLAREAPI": {
"apikey": "$CF_KEY",
"apitoken": "$CF_TOKEN",
"apiuser": "$CF_USER",
"domain": "$CF_DOMAIN"
"apikey": "$CLOUDFLAREAPI_KEY",
"apitoken": "$CLOUDFLAREAPI_TOKEN",
"apiuser": "$CLOUDFLAREAPI_USER",
"domain": "$CLOUDFLAREAPI_DOMAIN"
},
"CLOUDFLAREAPI_OLD": {
"apikey": "$CF_KEY",
@ -39,8 +39,8 @@
"domain": "$CLOUDNS_DOMAIN"
},
"DIGITALOCEAN": {
"domain": "$DO_DOMAIN",
"token": "$DO_TOKEN"
"domain": "$DIGITALOCEAN_DOMAIN",
"token": "$DIGITALOCEAN_TOKEN"
},
"DNSIMPLE": {
"baseurl": "https://api.sandbox.dnsimple.com",
@ -74,8 +74,8 @@
"HETZNER": {
"api_key": "$HETZNER_API_KEY",
"domain": "$HETZNER_DOMAIN",
"start_with_default_rate_limit": "true",
"optimize_for_rate_limit_quota": "Hour"
"optimize_for_rate_limit_quota": "Hour",
"start_with_default_rate_limit": "true"
},
"HEXONET": {
"apientity": "$HEXONET_ENTITY",
@ -132,9 +132,9 @@
"servername": "$POWERDNS_SERVERNAME"
},
"ROUTE53": {
"KeyId": "$R53_KEY_ID",
"SecretKey": "$R53_KEY",
"domain": "$R53_DOMAIN"
"KeyId": "$ROUTE53_KEY_ID",
"SecretKey": "$ROUTE53_KEY",
"domain": "$ROUTE53_DOMAIN"
},
"SOFTLAYER": {
"api_key": "$SL_API_KEY",