2025-01-12 04:36:52 +08:00

647 lines
29 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="author" content="Hadley Wickham" />
<title>Managing secrets</title>
<script>// Pandoc 2.9 adds attributes on both header and div. We remove the former (to
// be compatible with the behavior of Pandoc < 2.8).
document.addEventListener('DOMContentLoaded', function(e) {
var hs = document.querySelectorAll("div.section[class*='level'] > :first-child");
var i, h, a;
for (i = 0; i < hs.length; i++) {
h = hs[i];
if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6
a = h.attributes;
while (a.length > 0) h.removeAttribute(a[0].name);
}
});
</script>
<style type="text/css">
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
</style>
<style type="text/css">
code {
white-space: pre;
}
.sourceCode {
overflow: visible;
}
</style>
<style type="text/css" data-origin="pandoc">
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; }
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.at { color: #7d9029; }
code span.bn { color: #40a070; }
code span.bu { color: #008000; }
code span.cf { color: #007020; font-weight: bold; }
code span.ch { color: #4070a0; }
code span.cn { color: #880000; }
code span.co { color: #60a0b0; font-style: italic; }
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.do { color: #ba2121; font-style: italic; }
code span.dt { color: #902000; }
code span.dv { color: #40a070; }
code span.er { color: #ff0000; font-weight: bold; }
code span.ex { }
code span.fl { color: #40a070; }
code span.fu { color: #06287e; }
code span.im { color: #008000; font-weight: bold; }
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; }
code span.kw { color: #007020; font-weight: bold; }
code span.op { color: #666666; }
code span.ot { color: #007020; }
code span.pp { color: #bc7a00; }
code span.sc { color: #4070a0; }
code span.ss { color: #bb6688; }
code span.st { color: #4070a0; }
code span.va { color: #19177c; }
code span.vs { color: #4070a0; }
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; }
</style>
<script>
// apply pandoc div.sourceCode style to pre.sourceCode instead
(function() {
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].ownerNode.dataset["origin"] !== "pandoc") continue;
try { var rules = sheets[i].cssRules; } catch (e) { continue; }
var j = 0;
while (j < rules.length) {
var rule = rules[j];
// check if there is a div.sourceCode rule
if (rule.type !== rule.STYLE_RULE || rule.selectorText !== "div.sourceCode") {
j++;
continue;
}
var style = rule.style.cssText;
// check if color or background-color is set
if (rule.style.color === '' && rule.style.backgroundColor === '') {
j++;
continue;
}
// replace div.sourceCode by a pre.sourceCode rule
sheets[i].deleteRule(j);
sheets[i].insertRule('pre.sourceCode{' + style + '}', j);
}
}
})();
</script>
<style type="text/css">body {
background-color: #fff;
margin: 1em auto;
max-width: 700px;
overflow: visible;
padding-left: 2em;
padding-right: 2em;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.35;
}
#TOC {
clear: both;
margin: 0 0 10px 10px;
padding: 4px;
width: 400px;
border: 1px solid #CCCCCC;
border-radius: 5px;
background-color: #f6f6f6;
font-size: 13px;
line-height: 1.3;
}
#TOC .toctitle {
font-weight: bold;
font-size: 15px;
margin-left: 5px;
}
#TOC ul {
padding-left: 40px;
margin-left: -1.5em;
margin-top: 5px;
margin-bottom: 5px;
}
#TOC ul ul {
margin-left: -2em;
}
#TOC li {
line-height: 16px;
}
table {
margin: 1em auto;
border-width: 1px;
border-color: #DDDDDD;
border-style: outset;
border-collapse: collapse;
}
table th {
border-width: 2px;
padding: 5px;
border-style: inset;
}
table td {
border-width: 1px;
border-style: inset;
line-height: 18px;
padding: 5px 5px;
}
table, table th, table td {
border-left-style: none;
border-right-style: none;
}
table thead, table tr.even {
background-color: #f7f7f7;
}
p {
margin: 0.5em 0;
}
blockquote {
background-color: #f6f6f6;
padding: 0.25em 0.75em;
}
hr {
border-style: solid;
border: none;
border-top: 1px solid #777;
margin: 28px 0;
}
dl {
margin-left: 0;
}
dl dd {
margin-bottom: 13px;
margin-left: 13px;
}
dl dt {
font-weight: bold;
}
ul {
margin-top: 0;
}
ul li {
list-style: circle outside;
}
ul ul {
margin-bottom: 0;
}
pre, code {
background-color: #f7f7f7;
border-radius: 3px;
color: #333;
white-space: pre-wrap;
}
pre {
border-radius: 3px;
margin: 5px 0px 10px 0px;
padding: 10px;
}
pre:not([class]) {
background-color: #f7f7f7;
}
code {
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 85%;
}
p > code, li > code {
padding: 2px 0px;
}
div.figure {
text-align: center;
}
img {
background-color: #FFFFFF;
padding: 2px;
border: 1px solid #DDDDDD;
border-radius: 3px;
border: 1px solid #CCCCCC;
margin: 0 5px;
}
h1 {
margin-top: 0;
font-size: 35px;
line-height: 40px;
}
h2 {
border-bottom: 4px solid #f7f7f7;
padding-top: 10px;
padding-bottom: 2px;
font-size: 145%;
}
h3 {
border-bottom: 2px solid #f7f7f7;
padding-top: 10px;
font-size: 120%;
}
h4 {
border-bottom: 1px solid #f7f7f7;
margin-left: 8px;
font-size: 105%;
}
h5, h6 {
border-bottom: 1px solid #ccc;
font-size: 105%;
}
a {
color: #0033dd;
text-decoration: none;
}
a:hover {
color: #6666ff; }
a:visited {
color: #800080; }
a:visited:hover {
color: #BB00BB; }
a[href^="http:"] {
text-decoration: underline; }
a[href^="https:"] {
text-decoration: underline; }
code > span.kw { color: #555; font-weight: bold; }
code > span.dt { color: #902000; }
code > span.dv { color: #40a070; }
code > span.bn { color: #d14; }
code > span.fl { color: #d14; }
code > span.ch { color: #d14; }
code > span.st { color: #d14; }
code > span.co { color: #888888; font-style: italic; }
code > span.ot { color: #007020; }
code > span.al { color: #ff0000; font-weight: bold; }
code > span.fu { color: #900; font-weight: bold; }
code > span.er { color: #a61717; background-color: #e3d2d2; }
</style>
</head>
<body>
<h1 class="title toc-ignore">Managing secrets</h1>
<h4 class="author">Hadley Wickham</h4>
<div id="introduction" class="section level2">
<h2>Introduction</h2>
<p>This document gives you the basics on securely managing secrets. Most
of this document is not directly related to httr, but its common to
have some secrets to manage whenever you are using an API.</p>
<p>What is a secret? Some secrets are short alphanumeric sequences:</p>
<ul>
<li><p>Passwords are clearly secrets, e.g. the second argument to
<code>authenticate()</code>. Passwords are particularly important
because people (ill-advisedly) often use the same password in multiple
places.</p></li>
<li><p>Personal access tokens (e.g. <a href="https://github.blog/2013-05-16-personal-api-tokens/">github</a>)
should be kept secret: they are basically equivalent to a user name
password combination, but are slightly safer because you can have
multiple tokens for different purposes and its easy to invalidate one
token without affecting the others.</p></li>
</ul>
<p>Surprisingly, the “client secret” in an <code>oauth_app()</code> is
<strong>not</strong> a secret. Its not equivalent to a password, and if
you are writing an API wrapper package, it should be included in the
package. (If you dont believe me, here are <a href="https://developers.google.com/identity/protocols/oauth2">googles
comments on the topic</a>.)</p>
<p>Other secrets are files:</p>
<ul>
<li><p>The JSON web token (jwt) used for server-to-server OAuth (e.g. <a href="https://developers.google.com/identity/protocols/oauth2/service-account">google</a>)
is a secret because its equivalent to a personal access token.</p></li>
<li><p>The <code>.httr-oauth</code> file is a secret because it stores
OAuth access tokens.</p></li>
</ul>
<p>The goal of this vignette is to give you the tools to manage these
secrets in a secure way. Well start with best practices for managing
secrets locally, then talk about sharing secrets with selected others
(including travis), and finish with the challenges that CRAN
presents.</p>
<p>Here, I assume that the main threat is accidentally sharing your
secrets when you dont want to. Protecting against a committed attacker
is much harder. And if someone has already hacked your computer to the
point where they can run code, theres almost nothing you can do. If
youre concerned about those scenarios, youll need to take a more
comprehensive approach thats outside the scope of this document.</p>
</div>
<div id="locally" class="section level2">
<h2>Locally</h2>
<p>Working with secret files locally is straightforward because its ok
to store them in your project directory as long as you take three
precautions:</p>
<ul>
<li><p>Ensure the file is only readable by you, not by any other user on
the system. You can use the R function <code>Sys.chmod()</code> to do
so:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="fu">Sys.chmod</span>(<span class="st">&quot;secret.file&quot;</span>, <span class="at">mode =</span> <span class="st">&quot;0400&quot;</span>)</span></code></pre></div>
<p>Its good practice to verify this setting by examining the file
metadata with your local filesystem GUI tools or commands.</p></li>
<li><p>If you use git: make sure the files are listed in
<code>.gitignore</code> so they dont accidentally get included in a
public repository.</p></li>
<li><p>If youre making a package: make sure they are listed in
<code>.Rbuildignore</code> so they dont accidentally get included in a
public R package.</p></li>
</ul>
<p>httr proactively takes all of these steps for you whenever it creates
a <code>.httr-oauth</code> file.</p>
<p>The main remaining risk is that you might share the entire directory
(i.e. zipping and emailing, or in a public dropbox directory). If youre
worried about this scenario, store your secret files outside of the
project directory. If you do this, make sure to provide a helper
function to locate the file and provide an informative message if its
missing.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb2-1"><a href="#cb2-1" tabindex="-1"></a>my_secrets <span class="ot">&lt;-</span> <span class="cf">function</span>() {</span>
<span id="cb2-2"><a href="#cb2-2" tabindex="-1"></a> path <span class="ot">&lt;-</span> <span class="st">&quot;~/secrets/secret.json&quot;</span></span>
<span id="cb2-3"><a href="#cb2-3" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">file.exists</span>(path)) {</span>
<span id="cb2-4"><a href="#cb2-4" tabindex="-1"></a> <span class="fu">stop</span>(<span class="st">&quot;Can&#39;t find secret file: &#39;&quot;</span>, path, <span class="st">&quot;&#39;&quot;</span>)</span>
<span id="cb2-5"><a href="#cb2-5" tabindex="-1"></a> }</span>
<span id="cb2-6"><a href="#cb2-6" tabindex="-1"></a> </span>
<span id="cb2-7"><a href="#cb2-7" tabindex="-1"></a> jsonlite<span class="sc">::</span><span class="fu">read_json</span>(path)</span>
<span id="cb2-8"><a href="#cb2-8" tabindex="-1"></a>}</span></code></pre></div>
<p>Storing short secrets is harder because its tempting to record them
as a variable in your R script. This is a bad idea, because you end up
with a file that contains a mix of secret and public code. Instead, you
have three options:</p>
<ul>
<li>Ask for the secret each time.</li>
<li>Store in an environment variable.</li>
<li>Use the keyring package.</li>
</ul>
<p>Regardless of how you store them, to use your secrets you will still
need to read them into R variables. Be careful not to expose them by
printing them or saving them to a file.</p>
<div id="ask-each-time" class="section level3">
<h3>Ask each time</h3>
<p>For scripts that you only use every now and then, a simple solution
is to simply ask for the password each time the script is run. If you
use RStudio an easy and secure way to request a password is with the
rstudioapi package:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb3-1"><a href="#cb3-1" tabindex="-1"></a>password <span class="ot">&lt;-</span> rstudioapi<span class="sc">::</span><span class="fu">askForPassword</span>()</span></code></pre></div>
<p>If you dont use RStudio, use a more general solution like the <a href="https://github.com/wrathematics/getPass">getPass</a> package.</p>
<p>You should <strong>never</strong> type your password into the R
console: this will typically be stored in the <code>.Rhistory</code>
file, and its easy to accidentally share without realising it.</p>
</div>
<div id="environment-variables" class="section level3">
<h3>Environment variables</h3>
<p>Asking each time is a hassle, so you might want to store the secret
across sessions. One easy way to do that is with environment variables.
Environment variables, or <strong>envvars</strong> for short, are a
cross platform way of passing information to processes.</p>
<p>For passing envvars to R, you can list name-value pairs in a file
called <code>.Renviron</code> in your home directory. The easiest way to
edit it is to run:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1" tabindex="-1"></a><span class="fu">file.edit</span>(<span class="st">&quot;~/.Renviron&quot;</span>)</span></code></pre></div>
<p>The file looks something like</p>
<pre><code>VAR1 = value1
VAR2 = value2</code></pre>
<p>And you can access the values in R using
<code>Sys.getenv()</code>:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb6-1"><a href="#cb6-1" tabindex="-1"></a><span class="fu">Sys.getenv</span>(<span class="st">&quot;VAR1&quot;</span>)</span>
<span id="cb6-2"><a href="#cb6-2" tabindex="-1"></a><span class="co">#&gt; [1] &quot;value1&quot;</span></span></code></pre></div>
<p>Note that <code>.Renviron</code> is only processed on startup, so
youll need to restart R to see changes.</p>
<p>These environment variables will be available in every running R
process, and can easily be read by any other program on your computer to
access that file directly. For more security, use the keyring
package.</p>
</div>
<div id="keyring" class="section level3">
<h3>Keyring</h3>
<p>The <a href="https://github.com/r-lib/keyring">keyring</a> package
provides a way to store (and retrieve) data in your OSs secure secret
store. Keyring has a simple API:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb7-1"><a href="#cb7-1" tabindex="-1"></a>keyring<span class="sc">::</span><span class="fu">key_set</span>(<span class="st">&quot;MY_SECRET&quot;</span>)</span>
<span id="cb7-2"><a href="#cb7-2" tabindex="-1"></a>keyring<span class="sc">::</span><span class="fu">key_get</span>(<span class="st">&quot;MY_SECRET&quot;</span>)</span></code></pre></div>
<p>By default, keyring will use the system keyring. This is unlocked by
default when you log in, which means while the password is stored
securely pretty much any process can access it.</p>
<p>If you want to be even more secure, you can create custom keyring and
keep it locked. That will require you to enter a password every time you
want to access your secret.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb8-1"><a href="#cb8-1" tabindex="-1"></a>keyring<span class="sc">::</span><span class="fu">keyring_create</span>(<span class="st">&quot;httr&quot;</span>)</span>
<span id="cb8-2"><a href="#cb8-2" tabindex="-1"></a>keyring<span class="sc">::</span><span class="fu">key_set</span>(<span class="st">&quot;MY_SECRET&quot;</span>, <span class="at">keyring =</span> <span class="st">&quot;httr&quot;</span>)</span></code></pre></div>
<p>Note that accessing the key always unlocks the keyring, so if youre
being really careful, make sure to lock it again afterwards.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb9-1"><a href="#cb9-1" tabindex="-1"></a>keyring<span class="sc">::</span><span class="fu">keyring_lock</span>(<span class="st">&quot;httr&quot;</span>)</span></code></pre></div>
<p>You might wonder if weve actually achieved anything here because we
still need to enter a password! However, that one password lets you
access every secret, and you can control how often you need to re-enter
it by manually locking and unlocking the keyring.</p>
</div>
</div>
<div id="sharing-with-others" class="section level2">
<h2>Sharing with others</h2>
<p>By and large, managing secrets on your own computer is
straightforward. The challenge comes when you need to share them with
selected others:</p>
<ul>
<li><p>You may need to share a secret with me so that I can run your
reprex and figure out what is wrong with httr.</p></li>
<li><p>You might want to share a secret amongst a group of developers
all working on the same GitHub project.</p></li>
<li><p>You might want to automatically run authenticated tests on
travis.</p></li>
</ul>
<p>To make this work, all the techniques in this section rely on
<strong>public key cryptography</strong>. This is a type of asymmetric
encryption where you use a public key to produce content that can only
be decrypted by the holder of the matching private key.</p>
<div id="reprexes" class="section level3">
<h3>Reprexes</h3>
<p>The most common place you might need to share a secret is to generate
a reprex. First, do everything you can do eliminate the need to share a
secret:</p>
<ul>
<li>If it is an http problem, make sure to run all requests with
<code>verbose()</code>.</li>
<li>If you get an R error, make sure to include
<code>traceback()</code>.</li>
</ul>
<p>If youre lucky, that will be sufficient information to fix the
problem.</p>
<p>Otherwise, youll need to encrypt the secret so you can share it with
me. The easiest way to do so is with the following snippet:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb10-1"><a href="#cb10-1" tabindex="-1"></a><span class="fu">library</span>(openssl)</span>
<span id="cb10-2"><a href="#cb10-2" tabindex="-1"></a><span class="fu">library</span>(jsonlite)</span>
<span id="cb10-3"><a href="#cb10-3" tabindex="-1"></a><span class="fu">library</span>(curl)</span>
<span id="cb10-4"><a href="#cb10-4" tabindex="-1"></a></span>
<span id="cb10-5"><a href="#cb10-5" tabindex="-1"></a>encrypt <span class="ot">&lt;-</span> <span class="cf">function</span>(secret, username) {</span>
<span id="cb10-6"><a href="#cb10-6" tabindex="-1"></a> url <span class="ot">&lt;-</span> <span class="fu">paste</span>(<span class="st">&quot;https://api.github.com/users&quot;</span>, username, <span class="st">&quot;keys&quot;</span>, <span class="at">sep =</span> <span class="st">&quot;/&quot;</span>)</span>
<span id="cb10-7"><a href="#cb10-7" tabindex="-1"></a></span>
<span id="cb10-8"><a href="#cb10-8" tabindex="-1"></a> resp <span class="ot">&lt;-</span> httr<span class="sc">::</span><span class="fu">GET</span>(url)</span>
<span id="cb10-9"><a href="#cb10-9" tabindex="-1"></a> httr<span class="sc">::</span><span class="fu">stop_for_status</span>(resp)</span>
<span id="cb10-10"><a href="#cb10-10" tabindex="-1"></a> pubkey <span class="ot">&lt;-</span> httr<span class="sc">::</span><span class="fu">content</span>(resp)[[<span class="dv">1</span>]]<span class="sc">$</span>key</span>
<span id="cb10-11"><a href="#cb10-11" tabindex="-1"></a></span>
<span id="cb10-12"><a href="#cb10-12" tabindex="-1"></a> opubkey <span class="ot">&lt;-</span> openssl<span class="sc">::</span><span class="fu">read_pubkey</span>(pubkey)</span>
<span id="cb10-13"><a href="#cb10-13" tabindex="-1"></a> cipher <span class="ot">&lt;-</span> openssl<span class="sc">::</span><span class="fu">rsa_encrypt</span>(<span class="fu">charToRaw</span>(secret), opubkey)</span>
<span id="cb10-14"><a href="#cb10-14" tabindex="-1"></a> jsonlite<span class="sc">::</span><span class="fu">base64_enc</span>(cipher)</span>
<span id="cb10-15"><a href="#cb10-15" tabindex="-1"></a>}</span>
<span id="cb10-16"><a href="#cb10-16" tabindex="-1"></a> </span>
<span id="cb10-17"><a href="#cb10-17" tabindex="-1"></a>cipher <span class="ot">&lt;-</span> <span class="fu">encrypt</span>(<span class="st">&quot;&lt;username&gt;</span><span class="sc">\n</span><span class="st">&lt;password&gt;&quot;</span>, <span class="st">&quot;hadley&quot;</span>)</span>
<span id="cb10-18"><a href="#cb10-18" tabindex="-1"></a><span class="fu">cat</span>(cipher)</span></code></pre></div>
<p>Then I can run the following code on my computer to access it:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb11-1"><a href="#cb11-1" tabindex="-1"></a>decrypt <span class="ot">&lt;-</span> <span class="cf">function</span>(cipher, <span class="at">key =</span> openssl<span class="sc">::</span><span class="fu">my_key</span>()) {</span>
<span id="cb11-2"><a href="#cb11-2" tabindex="-1"></a> cipherraw <span class="ot">&lt;-</span> jsonlite<span class="sc">::</span><span class="fu">base64_dec</span>(cipher)</span>
<span id="cb11-3"><a href="#cb11-3" tabindex="-1"></a> <span class="fu">rawToChar</span>(openssl<span class="sc">::</span><span class="fu">rsa_decrypt</span>(cipherraw, <span class="at">key =</span> key))</span>
<span id="cb11-4"><a href="#cb11-4" tabindex="-1"></a>}</span>
<span id="cb11-5"><a href="#cb11-5" tabindex="-1"></a></span>
<span id="cb11-6"><a href="#cb11-6" tabindex="-1"></a><span class="fu">decrypt</span>(cipher)</span>
<span id="cb11-7"><a href="#cb11-7" tabindex="-1"></a><span class="co">#&gt; username</span></span>
<span id="cb11-8"><a href="#cb11-8" tabindex="-1"></a><span class="co">#&gt; password</span></span></code></pre></div>
<p>Change your password before and after you share it with me or anyone
else.</p>
</div>
<div id="github" class="section level3">
<h3>GitHub</h3>
<p>If you want to share secrets with a group of other people on GitHub,
use the <a href="https://github.com/gaborcsardi/secret">secret</a> or <a href="https://github.com/ropensci/cyphr">cyphr</a> packages.</p>
</div>
<div id="travis" class="section level3">
<h3>Travis</h3>
<p>The easiest way to handle short secrets is to use environment
variables. Youll set in your <code>.Renviron</code> locally and in the
settings pane on travis. That way you can use <code>Sys.getenv()</code>
to access in both places. Its also possible to set encrypted env vars
in your <code>.travis.yml</code>: see <a href="https://docs.travis-ci.com/user/environment-variables/">the
documentation</a> for details.</p>
<p>Regardless of how you set it, make sure you have a helper to retrieve
the value. A good error message will save you a lot of time when
debugging problems!</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb12-1"><a href="#cb12-1" tabindex="-1"></a>my_secret <span class="ot">&lt;-</span> <span class="cf">function</span>() {</span>
<span id="cb12-2"><a href="#cb12-2" tabindex="-1"></a> val <span class="ot">&lt;-</span> <span class="fu">Sys.getenv</span>(<span class="st">&quot;SECRET&quot;</span>)</span>
<span id="cb12-3"><a href="#cb12-3" tabindex="-1"></a> <span class="cf">if</span> (<span class="fu">identical</span>(val, <span class="st">&quot;&quot;</span>)) {</span>
<span id="cb12-4"><a href="#cb12-4" tabindex="-1"></a> <span class="fu">stop</span>(<span class="st">&quot;`SECRET` env var has not been set&quot;</span>)</span>
<span id="cb12-5"><a href="#cb12-5" tabindex="-1"></a> }</span>
<span id="cb12-6"><a href="#cb12-6" tabindex="-1"></a> val</span>
<span id="cb12-7"><a href="#cb12-7" tabindex="-1"></a>}</span></code></pre></div>
<p>Note that encrypted data is not available in pull requests in forks.
Typically youll need to check PRs locally once youve confirmed that
the code isnt actively malicious.</p>
<p>To share secret files on travis, see <a href="https://docs.travis-ci.com/user/encrypting-files/" class="uri">https://docs.travis-ci.com/user/encrypting-files/</a>.
Basically you will encrypt the file locally and check it in to git. Then
youll add a decryption step to your <code>.travis.yml</code> which
makes it decrypts it for each run.</p>
<p>Be careful to not accidentally expose the secret on travis. An easy
way to accidentally expose the secret is to print it out so that its
captured in the log. Dont do that!</p>
</div>
</div>
<div id="cran" class="section level2">
<h2>CRAN</h2>
<p>There is no way to securely share information with arbitrary R users,
including CRAN. That means that if youre developing a package, you need
to make sure that <code>R CMD check</code> passes cleanly even when
authentication is not available. This tends to primarily affect the
documentation, vignettes, and tests.</p>
<div id="documentation" class="section level3">
<h3>Documentation</h3>
<p>Like any R package, an API client needs clear and complete
documentation of all functions. Examples are particularly useful but may
need to be wrapped in <code>\donttest{}</code> to avoid challenges of
authentication, rate limiting, lack of network access, or occasional API
server down time.</p>
</div>
<div id="vignettes" class="section level3">
<h3>Vignettes</h3>
<p>Vignettes pose additional challenges when an API requires
authentication, because you dont want to bundle your own credentials
with the package! However, you can take advantage of the fact that the
vignette is built locally, and only checked by CRAN. In a setup chunk,
do:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb13-1"><a href="#cb13-1" tabindex="-1"></a>NOT_CRAN <span class="ot">&lt;-</span> <span class="fu">identical</span>(<span class="fu">tolower</span>(<span class="fu">Sys.getenv</span>(<span class="st">&quot;NOT_CRAN&quot;</span>)), <span class="st">&quot;true&quot;</span>)</span>
<span id="cb13-2"><a href="#cb13-2" tabindex="-1"></a>knitr<span class="sc">::</span>opts_chunk<span class="sc">$</span><span class="fu">set</span>(<span class="at">purl =</span> NOT_CRAN)</span></code></pre></div>
<p>And then use <code>eval = NOT_CRAN</code> in any chunk that requires
access to a secret.</p>
</div>
<div id="testing" class="section level3">
<h3>Testing</h3>
<p>Use <code>testthat::skip()</code> to automatically skip tests that
require authentication. I typically will wrap this into a little helper
function that I call at the start of every test requiring auth.</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb14-1"><a href="#cb14-1" tabindex="-1"></a>skip_if_no_auth <span class="ot">&lt;-</span> <span class="cf">function</span>() {</span>
<span id="cb14-2"><a href="#cb14-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="fu">identical</span>(<span class="fu">Sys.getenv</span>(<span class="st">&quot;MY_SECRET&quot;</span>), <span class="st">&quot;&quot;</span>)) {</span>
<span id="cb14-3"><a href="#cb14-3" tabindex="-1"></a> <span class="fu">skip</span>(<span class="st">&quot;No authentication available&quot;</span>)</span>
<span id="cb14-4"><a href="#cb14-4" tabindex="-1"></a> }</span>
<span id="cb14-5"><a href="#cb14-5" tabindex="-1"></a>}</span></code></pre></div>
</div>
</div>
<!-- code folding -->
<!-- dynamically load mathjax for compatibility with self-contained -->
<script>
(function () {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
document.getElementsByTagName("head")[0].appendChild(script);
})();
</script>
</body>
</html>