diff options
author | Kiran Kamineni <kiran.k.kamineni@intel.com> | 2018-05-04 16:50:39 -0700 |
---|---|---|
committer | Girish Havaldar <hg0071052@techmahindra.com> | 2018-05-15 17:21:53 +0000 |
commit | ac134cb099afa6be09fbc5cdad5db0bcf7aee08a (patch) | |
tree | cbcdf280db9285edfe8c6e62e094c6c4c6db302e /docs | |
parent | d4b81c05a255a847bbf7f08caebe032492ad2ca5 (diff) |
Adding a docs folder under sms repo
WORK IN PROGRESS
Adding a docs folder under sms
Issue-ID: AAF-185
Change-Id: I5ee3560cfda2100ad5207bb7e98d5cb9472e1325
Signed-off-by: Girish Havaldar <hg0071052@techmahindra.com>
Diffstat (limited to 'docs')
-rw-r--r-- | docs/api_swagger.html | 732 | ||||
-rw-r--r-- | docs/api_swagger.yaml | 288 | ||||
-rw-r--r-- | docs/apiswagger.rst | 745 | ||||
-rw-r--r-- | docs/coverage.html | 1471 | ||||
-rw-r--r-- | docs/coverage.md | 41 | ||||
-rw-r--r-- | docs/index.rst | 37 | ||||
-rw-r--r-- | docs/installation.rst | 33 | ||||
-rw-r--r-- | docs/sms_high_level.png | bin | 0 -> 141997 bytes | |||
-rw-r--r-- | docs/usage.rst | 54 |
9 files changed, 3401 insertions, 0 deletions
diff --git a/docs/api_swagger.html b/docs/api_swagger.html new file mode 100644 index 0000000..7f987a3 --- /dev/null +++ b/docs/api_swagger.html @@ -0,0 +1,732 @@ +<!doctype html> +<html> + <head> + <title>Secret Management Service</title> + <style type="text/css"> + body { + font-family: Trebuchet MS, sans-serif; + font-size: 15px; + color: #444; + margin-right: 24px; +} + +h1 { + font-size: 25px; +} +h2 { + font-size: 20px; +} +h3 { + font-size: 16px; + font-weight: bold; +} +hr { + height: 1px; + border: 0; + color: #ddd; + background-color: #ddd; +} + +.app-desc { + clear: both; + margin-left: 20px; +} +.param-name { + width: 100%; +} +.license-info { + margin-left: 20px; +} + +.license-url { + margin-left: 20px; +} + +.model { + margin: 0 0 0px 20px; +} + +.method { + margin-left: 20px; +} + +.method-notes { + margin: 10px 0 20px 0; + font-size: 90%; + color: #555; +} + +pre { + padding: 10px; + margin-bottom: 2px; +} + +.http-method { + text-transform: uppercase; +} + +pre.get { + background-color: #0f6ab4; +} + +pre.post { + background-color: #10a54a; +} + +pre.put { + background-color: #c5862b; +} + +pre.delete { + background-color: #a41e22; +} + +.huge { + color: #fff; +} + +pre.example { + background-color: #f3f3f3; + padding: 10px; + border: 1px solid #ddd; +} + +code { + white-space: pre; +} + +.nickname { + font-weight: bold; +} + +.method-path { + font-size: 1.5em; + background-color: #0f6ab4; +} + +.up { + float:right; +} + +.parameter { + width: 500px; +} + +.param { + width: 500px; + padding: 10px 0 0 20px; + font-weight: bold; +} + +.param-desc { + width: 700px; + padding: 0 0 0 20px; + color: #777; +} + +.param-type { + font-style: italic; +} + +.param-enum-header { +width: 700px; +padding: 0 0 0 60px; +color: #777; +font-weight: bold; +} + +.param-enum { +width: 700px; +padding: 0 0 0 80px; +color: #777; +font-style: italic; +} + +.field-label { + padding: 0; + margin: 0; + clear: both; +} + +.field-items { + padding: 0 0 15px 0; + margin-bottom: 15px; +} + +.return-type { + clear: both; + padding-bottom: 10px; +} + +.param-header { + font-weight: bold; +} + +.method-tags { + text-align: right; +} + +.method-tag { + background: none repeat scroll 0% 0% #24A600; + border-radius: 3px; + padding: 2px 10px; + margin: 2px; + color: #FFF; + display: inline-block; + text-decoration: none; +} + + </style> + </head> + <body> + <h1>Secret Management Service</h1> + <div class="app-desc">This is a service that provides secret management facilities</div> + <div class="app-desc">More information: <a href="https://helloreverb.com">https://helloreverb.com</a></div> + <div class="app-desc">Contact Info: <a href="kiran.k.kamineni@intel.com">kiran.k.kamineni@intel.com</a></div> + <div class="app-desc">Version: 1.0.0</div> + <div class="app-desc">BasePath:/v1/sms/</div> + <div class="license-info">Apache 2.0</div> + <div class="license-url">http://www.apache.org/licenses/LICENSE-2.0.html</div> + <h2>Access</h2> + <ol> + <li>APIKey KeyParamName:token KeyInQuery:false KeyInHeader:true</li> + </ol> + + <h2><a name="__Methods">Methods</a></h2> + [ Jump to <a href="#__Models">Models</a> ] + + <h3>Table of Contents </h3> + <div class="method-summary"></div> + <h4><a href="#Domain">Domain</a></h4> + <ul> + <li><a href="#domainDomainNameDelete"><code><span class="http-method">delete</span> /domain/{domainName}</code></a></li> + <li><a href="#domainPost"><code><span class="http-method">post</span> /domain</code></a></li> + </ul> + <h4><a href="#Login">Login</a></h4> + <ul> + <li><a href="#loginPost"><code><span class="http-method">post</span> /login</code></a></li> + </ul> + <h4><a href="#Secret">Secret</a></h4> + <ul> + <li><a href="#domainDomainNameSecretGet"><code><span class="http-method">get</span> /domain/{domainName}/secret</code></a></li> + <li><a href="#domainDomainNameSecretPost"><code><span class="http-method">post</span> /domain/{domainName}/secret</code></a></li> + <li><a href="#domainDomainNameSecretSecretNameDelete"><code><span class="http-method">delete</span> /domain/{domainName}/secret/{secretName}</code></a></li> + <li><a href="#domainDomainNameSecretSecretNameGet"><code><span class="http-method">get</span> /domain/{domainName}/secret/{secretName}</code></a></li> + </ul> + <h4><a href="#System">System</a></h4> + <ul> + <li><a href="#statusGet"><code><span class="http-method">get</span> /status</code></a></li> + <li><a href="#unsealPost"><code><span class="http-method">post</span> /unseal</code></a></li> + </ul> + + <h1><a name="Domain">Domain</a></h1> + <div class="method"><a name="domainDomainNameDelete"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="delete"><code class="huge"><span class="http-method">delete</span> /domain/{domainName}</code></pre></div> + <div class="method-summary">Deletes a domain by name (<span class="nickname">domainDomainNameDelete</span>)</div> + <div class="method-notes">Deletes a domain with provided name</div> + + <h3 class="field-label">Path parameters</h3> + <div class="field-items"> + <div class="param">domainName (required)</div> + + <div class="param-desc"><span class="param-type">Path Parameter</span> — Name of the domain </div> + </div> <!-- field-items --> + + + + + + + + <!--Todo: process Response Object and its headers, schema, examples --> + + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">204</h4> + Successful Deletion + <a href="#"></a> + <h4 class="field-label">404</h4> + Invalid Path or Path not found + <a href="#"></a> + </div> <!-- method --> + <hr/> + <div class="method"><a name="domainPost"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="post"><code class="huge"><span class="http-method">post</span> /domain</code></pre></div> + <div class="method-summary">Add a new domain (<span class="nickname">domainPost</span>)</div> + <div class="method-notes"></div> + + + <h3 class="field-label">Consumes</h3> + This API call consumes the following media types via the <span class="heaader">Content-Type</span> request header: + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Request body</h3> + <div class="field-items"> + <div class="param">body <a href="#Domain">Domain</a> (required)</div> + + <div class="param-desc"><span class="param-type">Body Parameter</span> — </div> + + </div> <!-- field-items --> + + + + + <h3 class="field-label">Return type</h3> + <div class="return-type"> + <a href="#Domain">Domain</a> + + </div> + + <!--Todo: process Response Object and its headers, schema, examples --> + + <h3 class="field-label">Example data</h3> + <div class="example-data-content-type">Content-Type: application/json</div> + <pre class="example"><code>{ + "name" : "name", + "uuid" : "uuid" +}</code></pre> + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">201</h4> + Successful Creation + <a href="#Domain">Domain</a> + <h4 class="field-label">400</h4> + Invalid input + <a href="#"></a> + <h4 class="field-label">500</h4> + Internal Server Error + <a href="#"></a> + </div> <!-- method --> + <hr/> + <h1><a name="Login">Login</a></h1> + <div class="method"><a name="loginPost"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="post"><code class="huge"><span class="http-method">post</span> /login</code></pre></div> + <div class="method-summary">Login with username and password (<span class="nickname">loginPost</span>)</div> + <div class="method-notes">Operations related to logging in via username and Password</div> + + + <h3 class="field-label">Consumes</h3> + This API call consumes the following media types via the <span class="heaader">Content-Type</span> request header: + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Request body</h3> + <div class="field-items"> + <div class="param">body <a href="#Credential">Credential</a> (required)</div> + + <div class="param-desc"><span class="param-type">Body Parameter</span> — </div> + + </div> <!-- field-items --> + + + + + <h3 class="field-label">Return type</h3> + <div class="return-type"> + <a href="#inline_response_200">inline_response_200</a> + + </div> + + <!--Todo: process Response Object and its headers, schema, examples --> + + <h3 class="field-label">Example data</h3> + <div class="example-data-content-type">Content-Type: application/json</div> + <pre class="example"><code>{ + "ttl" : 0, + "token" : "token" +}</code></pre> + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">200</h4> + Successful Login returns a token + <a href="#inline_response_200">inline_response_200</a> + <h4 class="field-label">404</h4> + Invalid Username or Password + <a href="#"></a> + </div> <!-- method --> + <hr/> + <h1><a name="Secret">Secret</a></h1> + <div class="method"><a name="domainDomainNameSecretGet"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="get"><code class="huge"><span class="http-method">get</span> /domain/{domainName}/secret</code></pre></div> + <div class="method-summary">List secret Names in this domain (<span class="nickname">domainDomainNameSecretGet</span>)</div> + <div class="method-notes">Gets all secret names in this domain</div> + + <h3 class="field-label">Path parameters</h3> + <div class="field-items"> + <div class="param">domainName (required)</div> + + <div class="param-desc"><span class="param-type">Path Parameter</span> — Name of the domain in which to look at </div> + </div> <!-- field-items --> + + + + + + + <h3 class="field-label">Return type</h3> + <div class="return-type"> + <a href="#inline_response_200_2">inline_response_200_2</a> + + </div> + + <!--Todo: process Response Object and its headers, schema, examples --> + + <h3 class="field-label">Example data</h3> + <div class="example-data-content-type">Content-Type: application/json</div> + <pre class="example"><code>"{\"secretnames\":[\"secretname1\",\"secretname2\",\"secretname3\"]}"</code></pre> + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">200</h4> + Successful operation + <a href="#inline_response_200_2">inline_response_200_2</a> + <h4 class="field-label">404</h4> + Invalid Path or Path not found + <a href="#"></a> + </div> <!-- method --> + <hr/> + <div class="method"><a name="domainDomainNameSecretPost"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="post"><code class="huge"><span class="http-method">post</span> /domain/{domainName}/secret</code></pre></div> + <div class="method-summary">Add a new secret (<span class="nickname">domainDomainNameSecretPost</span>)</div> + <div class="method-notes"></div> + + <h3 class="field-label">Path parameters</h3> + <div class="field-items"> + <div class="param">domainName (required)</div> + + <div class="param-desc"><span class="param-type">Path Parameter</span> — Name of the domain </div> + </div> <!-- field-items --> + + <h3 class="field-label">Consumes</h3> + This API call consumes the following media types via the <span class="heaader">Content-Type</span> request header: + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Request body</h3> + <div class="field-items"> + <div class="param">body <a href="#Secret">Secret</a> (required)</div> + + <div class="param-desc"><span class="param-type">Body Parameter</span> — </div> + + </div> <!-- field-items --> + + + + + + <!--Todo: process Response Object and its headers, schema, examples --> + + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">201</h4> + Successful Creation + <a href="#"></a> + <h4 class="field-label">404</h4> + Invalid Path or Path not found + <a href="#"></a> + </div> <!-- method --> + <hr/> + <div class="method"><a name="domainDomainNameSecretSecretNameDelete"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="delete"><code class="huge"><span class="http-method">delete</span> /domain/{domainName}/secret/{secretName}</code></pre></div> + <div class="method-summary">Deletes a Secret (<span class="nickname">domainDomainNameSecretSecretNameDelete</span>)</div> + <div class="method-notes"></div> + + <h3 class="field-label">Path parameters</h3> + <div class="field-items"> + <div class="param">secretName (required)</div> + + <div class="param-desc"><span class="param-type">Path Parameter</span> — Name of Secret to Delete </div><div class="param">domainName (required)</div> + + <div class="param-desc"><span class="param-type">Path Parameter</span> — Path to the SecretDomain which contains the Secret </div> + </div> <!-- field-items --> + + + + + + + + <!--Todo: process Response Object and its headers, schema, examples --> + + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">204</h4> + Successful Deletion + <a href="#"></a> + <h4 class="field-label">404</h4> + Invalid Path or Path not found + <a href="#"></a> + </div> <!-- method --> + <hr/> + <div class="method"><a name="domainDomainNameSecretSecretNameGet"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="get"><code class="huge"><span class="http-method">get</span> /domain/{domainName}/secret/{secretName}</code></pre></div> + <div class="method-summary">Find Secret by Name (<span class="nickname">domainDomainNameSecretSecretNameGet</span>)</div> + <div class="method-notes">Returns a single secret</div> + + <h3 class="field-label">Path parameters</h3> + <div class="field-items"> + <div class="param">domainName (required)</div> + + <div class="param-desc"><span class="param-type">Path Parameter</span> — Name of the domain in which to look at </div><div class="param">secretName (required)</div> + + <div class="param-desc"><span class="param-type">Path Parameter</span> — Name of the secret which is needed </div> + </div> <!-- field-items --> + + + + + + + <h3 class="field-label">Return type</h3> + <div class="return-type"> + <a href="#Secret">Secret</a> + + </div> + + <!--Todo: process Response Object and its headers, schema, examples --> + + <h3 class="field-label">Example data</h3> + <div class="example-data-content-type">Content-Type: application/json</div> + <pre class="example"><code>{ + "values" : { + "name" : "john", + "Age" : 40, + "admin" : true + }, + "name" : "name" +}</code></pre> + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">200</h4> + successful operation + <a href="#Secret">Secret</a> + <h4 class="field-label">404</h4> + Invalid Path or Path not found + <a href="#"></a> + </div> <!-- method --> + <hr/> + <h1><a name="System">System</a></h1> + <div class="method"><a name="statusGet"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="get"><code class="huge"><span class="http-method">get</span> /status</code></pre></div> + <div class="method-summary">Get backend status (<span class="nickname">statusGet</span>)</div> + <div class="method-notes">Gets current backend status. This API is used only by quorum clients</div> + + + + + + + + <h3 class="field-label">Return type</h3> + <div class="return-type"> + <a href="#inline_response_200_1">inline_response_200_1</a> + + </div> + + <!--Todo: process Response Object and its headers, schema, examples --> + + <h3 class="field-label">Example data</h3> + <div class="example-data-content-type">Content-Type: application/json</div> + <pre class="example"><code>{ + "sealstatus" : "sealstatus" +}</code></pre> + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">200</h4> + Successful operation + <a href="#inline_response_200_1">inline_response_200_1</a> + <h4 class="field-label">404</h4> + Invalid Path or Path not found + <a href="#"></a> + </div> <!-- method --> + <hr/> + <div class="method"><a name="unsealPost"/> + <div class="method-path"> + <a class="up" href="#__Methods">Up</a> + <pre class="post"><code class="huge"><span class="http-method">post</span> /unseal</code></pre></div> + <div class="method-summary">Unseal backend (<span class="nickname">unsealPost</span>)</div> + <div class="method-notes">Sends unseal shard to unseal if backend is sealed</div> + + + <h3 class="field-label">Consumes</h3> + This API call consumes the following media types via the <span class="heaader">Content-Type</span> request header: + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Request body</h3> + <div class="field-items"> + <div class="param">body <a href="#body">body</a> (required)</div> + + <div class="param-desc"><span class="param-type">Body Parameter</span> — </div> + + </div> <!-- field-items --> + + + + + + <!--Todo: process Response Object and its headers, schema, examples --> + + + <h3 class="field-label">Produces</h3> + This API call produces the following media types according to the <span class="header">Accept</span> request header; + the media type will be conveyed by the <span class="heaader">Content-Type</span> response header. + <ul> + <li><code>application/json</code></li> + </ul> + + <h3 class="field-label">Responses</h3> + <h4 class="field-label">201</h4> + Submitted unseal key + <a href="#"></a> + <h4 class="field-label">404</h4> + Invalid Path or Path not found + <a href="#"></a> + </div> <!-- method --> + <hr/> + + <h2><a name="__Models">Models</a></h2> + [ Jump to <a href="#__Methods">Methods</a> ] + + <h3>Table of Contents</h3> + <ol> + <li><a href="#Credential"><code>Credential</code> - </a></li> + <li><a href="#Domain"><code>Domain</code> - </a></li> + <li><a href="#Secret"><code>Secret</code> - </a></li> + <li><a href="#body"><code>body</code> - </a></li> + <li><a href="#inline_response_200"><code>inline_response_200</code> - </a></li> + <li><a href="#inline_response_200_1"><code>inline_response_200_1</code> - </a></li> + <li><a href="#inline_response_200_2"><code>inline_response_200_2</code> - </a></li> + </ol> + + <div class="model"> + <h3><a name="Credential"><code>Credential</code> - </a> <a class="up" href="#__Models">Up</a></h3> + <div class='model-description'></div> + <div class="field-items"> + <div class="param">username (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> </div> +<div class="param">password (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> </div> + </div> <!-- field-items --> + </div> + <div class="model"> + <h3><a name="Domain"><code>Domain</code> - </a> <a class="up" href="#__Models">Up</a></h3> + <div class='model-description'></div> + <div class="field-items"> + <div class="param">uuid (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> Optional value provided by user. If user does not provide, server will auto generate </div> +<div class="param">name (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> Name of the secret domain under which all secrets will be stored </div> + </div> <!-- field-items --> + </div> + <div class="model"> + <h3><a name="Secret"><code>Secret</code> - </a> <a class="up" href="#__Models">Up</a></h3> + <div class='model-description'></div> + <div class="field-items"> + <div class="param">name (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> Name of the secret </div> +<div class="param">values (optional)</div><div class="param-desc"><span class="param-type"><a href="#object">map[String, Object]</a></span> Map of key value pairs that constitute the secret </div> + </div> <!-- field-items --> + </div> + <div class="model"> + <h3><a name="body"><code>body</code> - </a> <a class="up" href="#__Models">Up</a></h3> + + <div class="field-items"> + <div class="param">unsealshard (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> Unseal shard that will be used along with other shards to unseal backend </div> + </div> <!-- field-items --> + </div> + <div class="model"> + <h3><a name="inline_response_200"><code>inline_response_200</code> - </a> <a class="up" href="#__Models">Up</a></h3> + + <div class="field-items"> + <div class="param">token (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> </div> +<div class="param">ttl (optional)</div><div class="param-desc"><span class="param-type"><a href="#integer">Integer</a></span> ttl of returned token in seconds </div> + </div> <!-- field-items --> + </div> + <div class="model"> + <h3><a name="inline_response_200_1"><code>inline_response_200_1</code> - </a> <a class="up" href="#__Models">Up</a></h3> + + <div class="field-items"> + <div class="param">sealstatus (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> seal status of backend </div> + </div> <!-- field-items --> + </div> + <div class="model"> + <h3><a name="inline_response_200_2"><code>inline_response_200_2</code> - </a> <a class="up" href="#__Models">Up</a></h3> + + <div class="field-items"> + <div class="param">secretnames (optional)</div><div class="param-desc"><span class="param-type"><a href="#string">array[String]</a></span> Array of strings referencing the secret names </div> + </div> <!-- field-items --> + </div> + </body> +</html> diff --git a/docs/api_swagger.yaml b/docs/api_swagger.yaml new file mode 100644 index 0000000..61cd091 --- /dev/null +++ b/docs/api_swagger.yaml @@ -0,0 +1,288 @@ +swagger: '2.0' +info: + description: This is a service that provides secret management facilities + version: 1.0.0 + title: Secret Management Service + contact: + email: kiran.k.kamineni@intel.com + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +host: 'aaf.onap.org:10443' +basePath: /v1/sms/ +tags: + - name: system + description: Operations related to quorum client which are not useful to clients + - name: login + description: Operations related to username password based authentication + - name: domain + description: Operations related to Secret Domains + - name: secret + description: Operations related to Secrets +schemes: + - https +paths: + /login: + post: + tags: + - login + summary: Login with username and password + description: Operations related to logging in via username and Password + consumes: + - application/json + produces: + - application/json + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Credential' + responses: + '200': + description: Successful Login returns a token + schema: + type: object + properties: + token: + type: string + ttl: + type: integer + description: ttl of returned token in seconds + '404': + description: Invalid Username or Password + /status: + get: + tags: + - system + description: Gets current backend status. This API is used only by quorum clients + summary: Get backend status + produces: + - application/json + responses: + '200': + description: Successful operation + schema: + type: object + properties: + sealstatus: + type: string + description: seal status of backend + '404': + description: Invalid Path or Path not found + /unseal: + post: + tags: + - system + description: Sends unseal shard to unseal if backend is sealed + summary: Unseal backend + consumes: + - application/json + produces: + - application/json + parameters: + - in: body + name: body + required: true + schema: + type: object + properties: + unsealshard: + type: string + description: >- + Unseal shard that will be used along with other shards to + unseal backend + responses: + '201': + description: Submitted unseal key + '404': + description: Invalid Path or Path not found + /domain: + post: + tags: + - domain + summary: Add a new domain + description: '' + consumes: + - application/json + produces: + - application/json + parameters: + - in: body + name: body + required: true + schema: + $ref: '#/definitions/Domain' + responses: + '201': + description: Successful Creation + schema: + $ref: '#/definitions/Domain' + '400': + description: Invalid input + '500': + description: Internal Server Error + '/domain/{domainName}': + delete: + tags: + - domain + description: Deletes a domain with provided name + summary: Deletes a domain by name + produces: + - application/json + parameters: + - name: domainName + in: path + description: Name of the domain + required: true + type: string + responses: + '204': + description: Successful Deletion + '404': + description: Invalid Path or Path not found + '/domain/{domainName}/secret': + post: + tags: + - secret + summary: Add a new secret + description: '' + consumes: + - application/json + produces: + - application/json + parameters: + - name: domainName + in: path + description: Name of the domain + required: true + type: string + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Secret' + responses: + '201': + description: Successful Creation + '404': + description: Invalid Path or Path not found + get: + tags: + - secret + description: Gets all secret names in this domain + summary: List secret Names in this domain + produces: + - application/json + parameters: + - name: domainName + in: path + description: Name of the domain in which to look at + required: true + type: string + responses: + '200': + description: Successful operation + schema: + type: object + properties: + secretnames: + type: array + items: + type: string + description: Array of strings referencing the secret names + example: + secretnames: ["secretname1", "secretname2", "secretname3"] + '404': + description: Invalid Path or Path not found + '/domain/{domainName}/secret/{secretName}': + get: + tags: + - secret + summary: Find Secret by Name + description: Returns a single secret + produces: + - application/json + parameters: + - name: domainName + in: path + description: Name of the domain in which to look at + required: true + type: string + - name: secretName + in: path + description: Name of the secret which is needed + required: true + type: string + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Secret' + '404': + description: Invalid Path or Path not found + delete: + tags: + - secret + summary: Deletes a Secret + description: '' + produces: + - application/json + parameters: + - name: secretName + in: path + description: Name of Secret to Delete + required: true + type: string + - name: domainName + in: path + required: true + description: Path to the SecretDomain which contains the Secret + type: string + responses: + '204': + description: Successful Deletion + '404': + description: Invalid Path or Path not found +securityDefinitions: + token: + type: apiKey + name: token + in: header +definitions: + Credential: + type: object + properties: + username: + type: string + password: + type: string + Domain: + type: object + properties: + uuid: + type: string + description: >- + Optional value provided by user. If user does not provide, server will + auto generate + name: + type: string + description: Name of the secret domain under which all secrets will be stored + Secret: + type: object + properties: + name: + type: string + description: Name of the secret + values: + description: Map of key value pairs that constitute the secret + type: object + additionalProperties: + type: object + example: + name: john + Age: 40 + admin: true +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' diff --git a/docs/apiswagger.rst b/docs/apiswagger.rst new file mode 100644 index 0000000..e35c6e8 --- /dev/null +++ b/docs/apiswagger.rst @@ -0,0 +1,745 @@ +SMS 1.0.0 API +=============================== + +.. toctree:: + :maxdepth: 3 + + +Description +~~~~~~~~~~~ + +This is a service that provides secret management facilities + + + +Contact Information +~~~~~~~~~~~~~~~~~~~ + + + +kiran.k.kamineni@intel.com + + + + + +License +~~~~~~~ + + +`Apache 2.0 <http://www.apache.org/licenses/LICENSE-2.0.html>`_ + + + + +Base URL +~~~~~~~~ + +https://aaf.onap.org:10443/v1/sms/ + +Security +~~~~~~~~ + + +.. _securities_token: + +token (API Key) +--------------- + + + +**Name:** token + +**Located in:** header + + + + +DOMAIN +~~~~~~ + + +Operations related to Secret Domains + + + + + +DELETE ``/domain/{domainName}`` +------------------------------- + + +Summary ++++++++ + +Deletes a domain by name + +Description ++++++++++++ + +.. raw:: html + + Deletes a domain with provided name + +Parameters +++++++++++ + +.. csv-table:: + :delim: | + :header: "Name", "Located in", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 15, 10, 10, 10, 20, 30 + + domainName | path | Yes | string | | | Name of the domain + + +Request ++++++++ + + +Responses ++++++++++ + +**204** +^^^^^^^ + +Successful Deletion + + +**404** +^^^^^^^ + +Invalid Path or Path not found + + + + + + +POST ``/domain`` +---------------- + + +Summary ++++++++ + +Add a new domain + + + +Request ++++++++ + + + +.. _d_c7bdcff9aff0692da98e588abdbc895b: + +Body +^^^^ + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + name | No | string | | | Name of the secret domain under which all secrets will be stored + uuid | No | string | | | Optional value provided by user. If user does not provide, server will auto generate + +.. code-block:: javascript + + { + "name": "somestring", + "uuid": "somestring" + } + +Responses ++++++++++ + +**201** +^^^^^^^ + +Successful Creation + + +Type: :ref:`Domain <d_c7bdcff9aff0692da98e588abdbc895b>` + +**Example:** + +.. code-block:: javascript + + { + "name": "somestring", + "uuid": "somestring" + } + +**400** +^^^^^^^ + +Invalid input + + +**500** +^^^^^^^ + +Internal Server Error + + + + + +LOGIN +~~~~~ + + +Operations related to username password based authentication + + + + + +POST ``/login`` +--------------- + + +Summary ++++++++ + +Login with username and password + +Description ++++++++++++ + +.. raw:: html + + Operations related to logging in via username and Password + + +Request ++++++++ + + + +.. _d_8e36d758bad367e4538a291a5dd5355f: + +Body +^^^^ + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + password | No | string | | | + username | No | string | | | + +.. code-block:: javascript + + { + "password": "somestring", + "username": "somestring" + } + +Responses ++++++++++ + +**200** +^^^^^^^ + +Successful Login returns a token + + +.. _i_bbceffdf8441c1c476ca77c42ad12f85: + +**Response Schema:** + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + token | No | string | | | + ttl | No | integer | | | ttl of returned token in seconds + + +**Example:** + +.. code-block:: javascript + + { + "token": "somestring", + "ttl": 1 + } + +**404** +^^^^^^^ + +Invalid Username or Password + + + + + +SECRET +~~~~~~ + + +Operations related to Secrets + + + + + +DELETE ``/domain/{domainName}/secret/{secretName}`` +--------------------------------------------------- + + +Summary ++++++++ + +Deletes a Secret + + +Parameters +++++++++++ + +.. csv-table:: + :delim: | + :header: "Name", "Located in", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 15, 10, 10, 10, 20, 30 + + secretName | path | Yes | string | | | Name of Secret to Delete + domainName | path | Yes | string | | | Path to the SecretDomain which contains the Secret + + +Request ++++++++ + + +Responses ++++++++++ + +**204** +^^^^^^^ + +Successful Deletion + + +**404** +^^^^^^^ + +Invalid Path or Path not found + + + + + + +GET ``/domain/{domainName}/secret`` +----------------------------------- + + +Summary ++++++++ + +List secret Names in this domain + +Description ++++++++++++ + +.. raw:: html + + Gets all secret names in this domain + +Parameters +++++++++++ + +.. csv-table:: + :delim: | + :header: "Name", "Located in", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 15, 10, 10, 10, 20, 30 + + domainName | path | Yes | string | | | Name of the domain in which to look at + + +Request ++++++++ + + +Responses ++++++++++ + +**200** +^^^^^^^ + +Successful operation + + +.. _i_1dcddfd6f11cba3fb2516d3a61cd1b77: + +**Response Schema:** + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + secretnames | No | array of string | | | Array of strings referencing the secret names + + +**Example:** + +.. code-block:: javascript + + { + "secretnames": [ + "secretname1", + "secretname2", + "secretname3" + ] + } + +**404** +^^^^^^^ + +Invalid Path or Path not found + + + + + + +GET ``/domain/{domainName}/secret/{secretName}`` +------------------------------------------------ + + +Summary ++++++++ + +Find Secret by Name + +Description ++++++++++++ + +.. raw:: html + + Returns a single secret + +Parameters +++++++++++ + +.. csv-table:: + :delim: | + :header: "Name", "Located in", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 15, 10, 10, 10, 20, 30 + + domainName | path | Yes | string | | | Name of the domain in which to look at + secretName | path | Yes | string | | | Name of the secret which is needed + + +Request ++++++++ + + +Responses ++++++++++ + +**200** +^^^^^^^ + +successful operation + + +Type: :ref:`Secret <d_5e5fddd9ede6eb091e8496a9c55b84c3>` + +**Example:** + +.. code-block:: javascript + + { + "name": "somestring", + "values": { + "Age": 40, + "admin": true, + "name": "john" + } + } + +**404** +^^^^^^^ + +Invalid Path or Path not found + + + + + + +POST ``/domain/{domainName}/secret`` +------------------------------------ + + +Summary ++++++++ + +Add a new secret + + +Parameters +++++++++++ + +.. csv-table:: + :delim: | + :header: "Name", "Located in", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 15, 10, 10, 10, 20, 30 + + domainName | path | Yes | string | | | Name of the domain + + +Request ++++++++ + + + +.. _d_5e5fddd9ede6eb091e8496a9c55b84c3: + +Body +^^^^ + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + name | No | string | | | Name of the secret + values | No | :ref:`values <i_a9213c9639162b77082e257e19cca0d0>` | | | Map of key value pairs that constitute the secret + +.. _i_a9213c9639162b77082e257e19cca0d0: + +**Values schema:** + + +Map of key value pairs that constitute the secret + +Map of {"key":":ref:`values-mapped <m_4d863967ef9a9d9efdadd1b250c76bd6>`"} + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + + +.. code-block:: javascript + + { + "name": "somestring", + "values": { + "Age": 40, + "admin": true, + "name": "john" + } + } + +Responses ++++++++++ + +**201** +^^^^^^^ + +Successful Creation + + +**404** +^^^^^^^ + +Invalid Path or Path not found + + + + + +SYSTEM +~~~~~~ + + +Operations related to quorum client which are not useful to clients + + + + + +GET ``/status`` +--------------- + + +Summary ++++++++ + +Get backend status + +Description ++++++++++++ + +.. raw:: html + + Gets current backend status. This API is used only by quorum clients + + +Request ++++++++ + + +Responses ++++++++++ + +**200** +^^^^^^^ + +Successful operation + + +.. _i_ac1bc8e82eadbd8c03f852e15be4d03b: + +**Response Schema:** + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + sealstatus | No | string | | | seal status of backend + + +**Example:** + +.. code-block:: javascript + + { + "sealstatus": "somestring" + } + +**404** +^^^^^^^ + +Invalid Path or Path not found + + + + + + +POST ``/unseal`` +---------------- + + +Summary ++++++++ + +Unseal backend + +Description ++++++++++++ + +.. raw:: html + + Sends unseal shard to unseal if backend is sealed + + +Request ++++++++ + + + +.. _i_9d32e021ba68855cbb6e633520b7cd2d: + +Body +^^^^ + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + unsealshard | No | string | | | Unseal shard that will be used along with other shards to unseal backend + +.. code-block:: javascript + + { + "unsealshard": "somestring" + } + +Responses ++++++++++ + +**201** +^^^^^^^ + +Submitted unseal key + + +**404** +^^^^^^^ + +Invalid Path or Path not found + + + + + +Data Structures +~~~~~~~~~~~~~~~ + +.. _d_8e36d758bad367e4538a291a5dd5355f: + +Credential Model Structure +-------------------------- + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + password | No | string | | | + username | No | string | | | + +.. _d_c7bdcff9aff0692da98e588abdbc895b: + +Domain Model Structure +---------------------- + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + name | No | string | | | Name of the secret domain under which all secrets will be stored + uuid | No | string | | | Optional value provided by user. If user does not provide, server will auto generate + +.. _d_5e5fddd9ede6eb091e8496a9c55b84c3: + +Secret Model Structure +---------------------- + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + name | No | string | | | Name of the secret + values | No | :ref:`values <i_a9213c9639162b77082e257e19cca0d0>` | | | Map of key value pairs that constitute the secret + +.. _i_a9213c9639162b77082e257e19cca0d0: + +**Values schema:** + + +Map of key value pairs that constitute the secret + +Map of {"key":":ref:`values-mapped <m_4d863967ef9a9d9efdadd1b250c76bd6>`"} + +.. csv-table:: + :delim: | + :header: "Name", "Required", "Type", "Format", "Properties", "Description" + :widths: 20, 10, 15, 15, 30, 25 + + + diff --git a/docs/coverage.html b/docs/coverage.html new file mode 100644 index 0000000..39ee191 --- /dev/null +++ b/docs/coverage.html @@ -0,0 +1,1471 @@ + +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <style> + body { + background: black; + color: rgb(80, 80, 80); + } + body, pre, #legend span { + font-family: Menlo, monospace; + font-weight: bold; + } + #topbar { + background: black; + position: fixed; + top: 0; left: 0; right: 0; + height: 42px; + border-bottom: 1px solid rgb(80, 80, 80); + } + #content { + margin-top: 50px; + } + #nav, #legend { + float: left; + margin-left: 10px; + } + #legend { + margin-top: 12px; + } + #nav { + margin-top: 10px; + } + #legend span { + margin: 0 5px; + } + .cov0 { color: rgb(192, 0, 0) } +.cov1 { color: rgb(128, 128, 128) } +.cov2 { color: rgb(116, 140, 131) } +.cov3 { color: rgb(104, 152, 134) } +.cov4 { color: rgb(92, 164, 137) } +.cov5 { color: rgb(80, 176, 140) } +.cov6 { color: rgb(68, 188, 143) } +.cov7 { color: rgb(56, 200, 146) } +.cov8 { color: rgb(44, 212, 149) } +.cov9 { color: rgb(32, 224, 152) } +.cov10 { color: rgb(20, 236, 155) } + + </style> + </head> + <body> + <div id="topbar"> + <div id="nav"> + <select id="files"> + + <option value="file0">sms/auth/auth.go (76.1%)</option> + + <option value="file1">sms/backend/backend.go (80.0%)</option> + + <option value="file2">sms/backend/vault.go (72.5%)</option> + + <option value="file3">sms/config/config.go (78.6%)</option> + + <option value="file4">sms/handler/handler.go (63.0%)</option> + + <option value="file5">sms/log/logger.go (65.6%)</option> + + <option value="file6">sms/sms.go (77.8%)</option> + + </select> + </div> + <div id="legend"> + <span>not tracked</span> + + <span class="cov0">no coverage</span> + <span class="cov1">low coverage</span> + <span class="cov2">*</span> + <span class="cov3">*</span> + <span class="cov4">*</span> + <span class="cov5">*</span> + <span class="cov6">*</span> + <span class="cov7">*</span> + <span class="cov8">*</span> + <span class="cov9">*</span> + <span class="cov10">high coverage</span> + + </div> + </div> + <div id="content"> + + <pre class="file" id="file0" style="display: none">/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package auth + +import ( + "bytes" + "crypto" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" + "io/ioutil" + + smslogger "sms/log" +) + +// GetTLSConfig initializes a tlsConfig using the CA's certificate +// This config is then used to enable the server for mutual TLS +func GetTLSConfig(caCertFile string) (*tls.Config, error) <span class="cov10" title="3">{ + + // Initialize tlsConfig once + caCert, err := ioutil.ReadFile(caCertFile) + + if err != nil </span><span class="cov1" title="1">{ + return nil, err + }</span> + + <span class="cov6" title="2">caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + tlsConfig := &tls.Config{ + // Change to RequireAndVerify once we have mandatory certs + ClientAuth: tls.VerifyClientCertIfGiven, + ClientCAs: caCertPool, + MinVersion: tls.VersionTLS12, + } + tlsConfig.BuildNameToCertificate() + return tlsConfig, nil</span> +} + +// GeneratePGPKeyPair produces a PGP key pair and returns +// two things: +// A base64 encoded form of the public part of the entity +// A base64 encoded form of the private key +func GeneratePGPKeyPair() (string, string, error) <span class="cov10" title="3">{ + + var entity *openpgp.Entity + config := &packet.Config{ + DefaultHash: crypto.SHA256, + } + + entity, err := openpgp.NewEntity("aaf.sms.init", "PGP Key for unsealing", "", config) + if smslogger.CheckError(err, "Create Entity") != nil </span><span class="cov0" title="0">{ + return "", "", err + }</span> + + // Sign the identity in the entity + <span class="cov10" title="3">for _, id := range entity.Identities </span><span class="cov10" title="3">{ + err = id.SelfSignature.SignUserId(id.UserId.Id, entity.PrimaryKey, entity.PrivateKey, nil) + if smslogger.CheckError(err, "Sign Entity") != nil </span><span class="cov0" title="0">{ + return "", "", err + }</span> + } + + // Sign the subkey in the entity + <span class="cov10" title="3">for _, subkey := range entity.Subkeys </span><span class="cov10" title="3">{ + err := subkey.Sig.SignKey(subkey.PublicKey, entity.PrivateKey, nil) + if smslogger.CheckError(err, "Sign Subkey") != nil </span><span class="cov0" title="0">{ + return "", "", err + }</span> + } + + <span class="cov10" title="3">buffer := new(bytes.Buffer) + entity.Serialize(buffer) + pbkey := base64.StdEncoding.EncodeToString(buffer.Bytes()) + + buffer.Reset() + entity.SerializePrivate(buffer, nil) + prkey := base64.StdEncoding.EncodeToString(buffer.Bytes()) + + return pbkey, prkey, nil</span> +} + +// EncryptPGPString takes data and a public key and encrypts using that +// public key +func EncryptPGPString(data string, pbKey string) (string, error) <span class="cov6" title="2">{ + + pbKeyBytes, err := base64.StdEncoding.DecodeString(pbKey) + if smslogger.CheckError(err, "Decoding Base64 Public Key") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov6" title="2">dataBytes := []byte(data) + + pbEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(pbKeyBytes))) + if smslogger.CheckError(err, "Reading entity from PGP key") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + // encrypt string + <span class="cov6" title="2">buf := new(bytes.Buffer) + out, err := openpgp.Encrypt(buf, []*openpgp.Entity{pbEntity}, nil, nil, nil) + if smslogger.CheckError(err, "Creating Encryption Pipe") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov6" title="2">_, err = out.Write(dataBytes) + if smslogger.CheckError(err, "Writing to Encryption Pipe") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov6" title="2">err = out.Close() + if smslogger.CheckError(err, "Closing Encryption Pipe") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov6" title="2">crp := base64.StdEncoding.EncodeToString(buf.Bytes()) + return crp, nil</span> +} + +// DecryptPGPString decrypts a PGP encoded input string and returns +// a base64 representation of the decoded string +func DecryptPGPString(data string, prKey string) (string, error) <span class="cov1" title="1">{ + + // Convert private key to bytes from base64 + prKeyBytes, err := base64.StdEncoding.DecodeString(prKey) + if smslogger.CheckError(err, "Decoding Base64 Private Key") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov1" title="1">dataBytes, err := base64.StdEncoding.DecodeString(data) + if smslogger.CheckError(err, "Decoding base64 data") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov1" title="1">prEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(prKeyBytes))) + if smslogger.CheckError(err, "Read Entity") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov1" title="1">prEntityList := &openpgp.EntityList{prEntity} + message, err := openpgp.ReadMessage(bytes.NewBuffer(dataBytes), prEntityList, nil, nil) + if smslogger.CheckError(err, "Decrypting Message") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + + <span class="cov1" title="1">var retBuf bytes.Buffer + retBuf.ReadFrom(message.UnverifiedBody) + + return retBuf.String(), nil</span> +} + +// ReadFromFile reads a file and loads the PGP key into +// a string +func ReadFromFile(fileName string) (string, error) <span class="cov6" title="2">{ + + data, err := ioutil.ReadFile(fileName) + if smslogger.CheckError(err, "Read from file") != nil </span><span class="cov0" title="0">{ + return "", err + }</span> + <span class="cov6" title="2">return string(data), nil</span> +} + +// WriteToFile writes a PGP key into a file. +// It will truncate the file if it exists +func WriteToFile(data string, fileName string) error <span class="cov0" title="0">{ + + err := ioutil.WriteFile(fileName, []byte(data), 0600) + if smslogger.CheckError(err, "Write to file") != nil </span><span class="cov0" title="0">{ + return err + }</span> + <span class="cov0" title="0">return nil</span> +} +</pre> + + <pre class="file" id="file1" style="display: none">/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package backend + +import ( + smsconfig "sms/config" + smslogger "sms/log" +) + +// SecretDomain is where Secrets are stored. +// A single domain can have any number of secrets +type SecretDomain struct { + UUID string `json:"uuid"` + Name string `json:"name"` +} + +// Secret is the struct that defines the structure of a secret +// It consists of a name and map containing key value pairs +type Secret struct { + Name string `json:"name"` + Values map[string]interface{} `json:"values"` +} + +// SecretBackend interface that will be implemented for various secret backends +type SecretBackend interface { + Init() error + GetStatus() (bool, error) + Unseal(shard string) error + RegisterQuorum(pgpkey string) (string, error) + + GetSecret(dom string, sec string) (Secret, error) + ListSecret(dom string) ([]string, error) + + CreateSecretDomain(name string) (SecretDomain, error) + CreateSecret(dom string, sec Secret) error + + DeleteSecretDomain(name string) error + DeleteSecret(dom string, name string) error +} + +// InitSecretBackend returns an interface implementation +func InitSecretBackend() (SecretBackend, error) <span class="cov8" title="1">{ + backendImpl := &Vault{ + vaultAddress: smsconfig.SMSConfig.BackendAddress, + vaultToken: smsconfig.SMSConfig.VaultToken, + } + + err := backendImpl.Init() + if smslogger.CheckError(err, "InitSecretBackend") != nil </span><span class="cov0" title="0">{ + return nil, err + }</span> + + <span class="cov8" title="1">return backendImpl, nil</span> +} + +// LoginBackend Interface that will be implemented for various login backends +type LoginBackend interface { +} +</pre> + + <pre class="file" id="file2" style="display: none">/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package backend + +import ( + uuid "github.com/hashicorp/go-uuid" + vaultapi "github.com/hashicorp/vault/api" + smsauth "sms/auth" + smslogger "sms/log" + + "errors" + "fmt" + "strings" + "sync" + "time" +) + +// Vault is the main Struct used in Backend to initialize the struct +type Vault struct { + sync.Mutex + initRoleDone bool + policyName string + roleID string + secretID string + vaultAddress string + vaultClient *vaultapi.Client + vaultMountPrefix string + internalDomain string + internalDomainMounted bool + vaultTempTokenTTL time.Time + vaultToken string + shards []string + prkey string +} + +// initVaultClient will create the initial +// Vault strcuture and populate it with the +// right values and it will also create +// a vault client +func (v *Vault) initVaultClient() error <span class="cov6" title="11">{ + + vaultCFG := vaultapi.DefaultConfig() + vaultCFG.Address = v.vaultAddress + client, err := vaultapi.NewClient(vaultCFG) + if smslogger.CheckError(err, "Create new vault client") != nil </span><span class="cov0" title="0">{ + return err + }</span> + + <span class="cov6" title="11">v.initRoleDone = false + v.policyName = "smsvaultpolicy" + v.vaultClient = client + v.vaultMountPrefix = "sms" + v.internalDomain = "smsinternaldomain" + v.internalDomainMounted = false + v.prkey = "" + return nil</span> +} + +// Init will initialize the vault connection +// It will also initialize vault if it is not +// already initialized. +// The initial policy will also be created +func (v *Vault) Init() error <span class="cov1" title="1">{ + + v.initVaultClient() + // Initialize vault if it is not already + // Returns immediately if it is initialized + v.initializeVault() + + err := v.initRole() + if smslogger.CheckError(err, "InitRole First Attempt") != nil </span><span class="cov0" title="0">{ + smslogger.WriteInfo("InitRole will try again later") + }</span> + + <span class="cov1" title="1">return nil</span> +} + +// GetStatus returns the current seal status of vault +func (v *Vault) GetStatus() (bool, error) <span class="cov3" title="3">{ + + sys := v.vaultClient.Sys() + sealStatus, err := sys.SealStatus() + if smslogger.CheckError(err, "Getting Status") != nil </span><span class="cov0" title="0">{ + return false, errors.New("Error getting status") + }</span> + + <span class="cov3" title="3">return sealStatus.Sealed, nil</span> +} + +// RegisterQuorum registers the PGP public key for a quorum client +// We will return a shard to the client that is registering +func (v *Vault) RegisterQuorum(pgpkey string) (string, error) <span class="cov0" title="0">{ + + v.Lock() + defer v.Unlock() + + if v.shards == nil </span><span class="cov0" title="0">{ + smslogger.WriteError("Invalid operation in RegisterQuorum") + return "", errors.New("Invalid operation") + }</span> + // Pop the slice + <span class="cov0" title="0">var sh string + sh, v.shards = v.shards[len(v.shards)-1], v.shards[:len(v.shards)-1] + if len(v.shards) == 0 </span><span class="cov0" title="0">{ + v.shards = nil + }</span> + + // Decrypt with SMS pgp Key + <span class="cov0" title="0">sh, _ = smsauth.DecryptPGPString(sh, v.prkey) + // Encrypt with Quorum client pgp key + sh, _ = smsauth.EncryptPGPString(sh, pgpkey) + + return sh, nil</span> +} + +// Unseal is a passthrough API that allows any +// unseal or initialization processes for the backend +func (v *Vault) Unseal(shard string) error <span class="cov0" title="0">{ + + sys := v.vaultClient.Sys() + _, err := sys.Unseal(shard) + if smslogger.CheckError(err, "Unseal Operation") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to execute unseal operation with specified shard") + }</span> + + <span class="cov0" title="0">return nil</span> +} + +// GetSecret returns a secret mounted on a particular domain name +// The secret itself is referenced via its name which translates to +// a mount path in vault +func (v *Vault) GetSecret(dom string, name string) (Secret, error) <span class="cov5" title="7">{ + + err := v.checkToken() + if smslogger.CheckError(err, "Tocken Check") != nil </span><span class="cov0" title="0">{ + return Secret{}, errors.New("Token check failed") + }</span> + + <span class="cov5" title="7">dom = v.vaultMountPrefix + "/" + dom + + sec, err := v.vaultClient.Logical().Read(dom + "/" + name) + if smslogger.CheckError(err, "Read Secret") != nil </span><span class="cov0" title="0">{ + return Secret{}, errors.New("Unable to read Secret at provided path") + }</span> + + // sec and err are nil in the case where a path does not exist + <span class="cov5" title="7">if sec == nil </span><span class="cov0" title="0">{ + smslogger.WriteWarn("Vault read was empty. Invalid Path") + return Secret{}, errors.New("Secret not found at the provided path") + }</span> + + <span class="cov5" title="7">return Secret{Name: name, Values: sec.Data}, nil</span> +} + +// ListSecret returns a list of secret names on a particular domain +// The values of the secret are not returned +func (v *Vault) ListSecret(dom string) ([]string, error) <span class="cov3" title="3">{ + + err := v.checkToken() + if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{ + return nil, errors.New("Token check failed") + }</span> + + <span class="cov3" title="3">dom = v.vaultMountPrefix + "/" + dom + + sec, err := v.vaultClient.Logical().List(dom) + if smslogger.CheckError(err, "Read Secret") != nil </span><span class="cov0" title="0">{ + return nil, errors.New("Unable to read Secret at provided path") + }</span> + + // sec and err are nil in the case where a path does not exist + <span class="cov3" title="3">if sec == nil </span><span class="cov0" title="0">{ + smslogger.WriteWarn("Vaultclient returned empty data") + return nil, errors.New("Secret not found at the provided path") + }</span> + + <span class="cov3" title="3">val, ok := sec.Data["keys"].([]interface{}) + if !ok </span><span class="cov0" title="0">{ + smslogger.WriteError("Secret not found at the provided path") + return nil, errors.New("Secret not found at the provided path") + }</span> + + <span class="cov3" title="3">retval := make([]string, len(val)) + for i, v := range val </span><span class="cov5" title="7">{ + retval[i] = fmt.Sprint(v) + }</span> + + <span class="cov3" title="3">return retval, nil</span> +} + +// Mounts the internal Domain if its not already mounted +func (v *Vault) mountInternalDomain(name string) error <span class="cov5" title="8">{ + + if v.internalDomainMounted </span><span class="cov1" title="1">{ + return nil + }</span> + + <span class="cov5" title="7">name = strings.TrimSpace(name) + mountPath := v.vaultMountPrefix + "/" + name + mountInput := &vaultapi.MountInput{ + Type: "kv", + Description: "Mount point for domain: " + name, + Local: false, + SealWrap: false, + Config: vaultapi.MountConfigInput{}, + } + + err := v.vaultClient.Sys().Mount(mountPath, mountInput) + if smslogger.CheckError(err, "Mount internal Domain") != nil </span><span class="cov1" title="1">{ + if strings.Contains(err.Error(), "existing mount") </span><span class="cov1" title="1">{ + // It is already mounted + v.internalDomainMounted = true + return nil + }</span> + // Ran into some other error mounting it. + <span class="cov0" title="0">return errors.New("Unable to mount internal Domain")</span> + } + + <span class="cov5" title="6">v.internalDomainMounted = true + return nil</span> +} + +// Stores the UUID created for secretdomain in vault +// under v.vaultMountPrefix / smsinternal domain +func (v *Vault) storeUUID(uuid string, name string) error <span class="cov5" title="8">{ + + // Check if token is still valid + err := v.checkToken() + if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{ + return errors.New("Token Check failed") + }</span> + + <span class="cov5" title="8">err = v.mountInternalDomain(v.internalDomain) + if smslogger.CheckError(err, "Mount Internal Domain") != nil </span><span class="cov0" title="0">{ + return err + }</span> + + <span class="cov5" title="8">secret := Secret{ + Name: name, + Values: map[string]interface{}{ + "uuid": uuid, + }, + } + + err = v.CreateSecret(v.internalDomain, secret) + if smslogger.CheckError(err, "Write UUID to domain") != nil </span><span class="cov0" title="0">{ + return err + }</span> + + <span class="cov5" title="8">return nil</span> +} + +// CreateSecretDomain mounts the kv backend on a path with the given name +func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) <span class="cov5" title="8">{ + + // Check if token is still valid + err := v.checkToken() + if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{ + return SecretDomain{}, errors.New("Token Check failed") + }</span> + + <span class="cov5" title="8">name = strings.TrimSpace(name) + mountPath := v.vaultMountPrefix + "/" + name + mountInput := &vaultapi.MountInput{ + Type: "kv", + Description: "Mount point for domain: " + name, + Local: false, + SealWrap: false, + Config: vaultapi.MountConfigInput{}, + } + + err = v.vaultClient.Sys().Mount(mountPath, mountInput) + if smslogger.CheckError(err, "Create Domain") != nil </span><span class="cov0" title="0">{ + return SecretDomain{}, errors.New("Unable to create Secret Domain") + }</span> + + <span class="cov5" title="8">uuid, _ := uuid.GenerateUUID() + err = v.storeUUID(uuid, name) + if smslogger.CheckError(err, "Store UUID") != nil </span><span class="cov0" title="0">{ + // Mount was successful at this point. + // Rollback the mount operation since we could not + // store the UUID for the mount. + v.vaultClient.Sys().Unmount(mountPath) + return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry") + }</span> + + <span class="cov5" title="8">return SecretDomain{uuid, name}, nil</span> +} + +// CreateSecret creates a secret mounted on a particular domain name +// The secret itself is mounted on a path specified by name +func (v *Vault) CreateSecret(dom string, sec Secret) error <span class="cov7" title="18">{ + + err := v.checkToken() + if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{ + return errors.New("Token check failed") + }</span> + + <span class="cov7" title="18">dom = v.vaultMountPrefix + "/" + dom + + // Vault return is empty on successful write + // TODO: Check if values is not empty + _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values) + if smslogger.CheckError(err, "Create Secret") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to create Secret at provided path") + }</span> + + <span class="cov7" title="18">return nil</span> +} + +// DeleteSecretDomain deletes a secret domain which translates to +// an unmount operation on the given path in Vault +func (v *Vault) DeleteSecretDomain(name string) error <span class="cov3" title="3">{ + + err := v.checkToken() + if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{ + return errors.New("Token Check Failed") + }</span> + + <span class="cov3" title="3">name = strings.TrimSpace(name) + mountPath := v.vaultMountPrefix + "/" + name + + err = v.vaultClient.Sys().Unmount(mountPath) + if smslogger.CheckError(err, "Delete Domain") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to delete domain specified") + }</span> + + <span class="cov3" title="3">return nil</span> +} + +// DeleteSecret deletes a secret mounted on the path provided +func (v *Vault) DeleteSecret(dom string, name string) error <span class="cov5" title="7">{ + + err := v.checkToken() + if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{ + return errors.New("Token check failed") + }</span> + + <span class="cov5" title="7">dom = v.vaultMountPrefix + "/" + dom + + // Vault return is empty on successful delete + _, err = v.vaultClient.Logical().Delete(dom + "/" + name) + if smslogger.CheckError(err, "Delete Secret") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to delete Secret at provided path") + }</span> + + <span class="cov5" title="7">return nil</span> +} + +// initRole is called only once during SMS bring up +// It initially creates a role and secret id associated with +// that role. Later restarts will use the existing role-id +// and secret-id stored on disk +func (v *Vault) initRole() error <span class="cov10" title="56">{ + + if v.initRoleDone </span><span class="cov9" title="48">{ + return nil + }</span> + + // Use the root token once here + <span class="cov5" title="8">v.vaultClient.SetToken(v.vaultToken) + defer v.vaultClient.ClearToken() + + // Check if roleID and secretID has already been created + rID, error := smsauth.ReadFromFile("auth/role") + if error != nil </span><span class="cov5" title="7">{ + smslogger.WriteWarn("Unable to find RoleID. Generating...") + }</span><span class="cov1" title="1"> else { + sID, error := smsauth.ReadFromFile("auth/secret") + if error != nil </span><span class="cov0" title="0">{ + smslogger.WriteWarn("Unable to find secretID. Generating...") + }</span><span class="cov1" title="1"> else { + v.roleID = rID + v.secretID = sID + v.initRoleDone = true + return nil + }</span> + } + + <span class="cov5" title="7">rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] } + path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }` + err := v.vaultClient.Sys().PutPolicy(v.policyName, rules) + if smslogger.CheckError(err, "Creating Policy") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to create policy for approle creation") + }</span> + + //Check if applrole is mounted + <span class="cov5" title="7">authMounts, err := v.vaultClient.Sys().ListAuth() + if smslogger.CheckError(err, "Mount Auth Backend") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to get mounted auth backends") + }</span> + + <span class="cov5" title="7">approleMounted := false + for k, v := range authMounts </span><span class="cov5" title="7">{ + if v.Type == "approle" && k == "approle/" </span><span class="cov0" title="0">{ + approleMounted = true + break</span> + } + } + + // Mount approle in case its not already mounted + <span class="cov5" title="7">if !approleMounted </span><span class="cov5" title="7">{ + v.vaultClient.Sys().EnableAuth("approle", "approle", "") + }</span> + + <span class="cov5" title="7">rName := v.vaultMountPrefix + "-role" + data := map[string]interface{}{ + "token_ttl": "60m", + "policies": [2]string{"default", v.policyName}, + } + + // Create a role-id + v.vaultClient.Logical().Write("auth/approle/role/"+rName, data) + sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id") + if smslogger.CheckError(err, "Create RoleID") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to create role ID for approle") + }</span> + <span class="cov5" title="7">v.roleID = sec.Data["role_id"].(string) + + // Create a secret-id to go with it + sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id", + map[string]interface{}{}) + if smslogger.CheckError(err, "Create SecretID") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to create secret ID for role") + }</span> + + <span class="cov5" title="7">v.secretID = sec.Data["secret_id"].(string) + v.initRoleDone = true + /* + * Revoke the Root token. + * If a new Root Token is needed, it will need to be created + * using the unseal shards. + */ + err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken) + if smslogger.CheckError(err, "Revoke Root Token") != nil </span><span class="cov0" title="0">{ + smslogger.WriteWarn("Unable to Revoke Token") + }</span><span class="cov5" title="7"> else { + // Revoked successfully and clear it + v.vaultToken = "" + }</span> + + // Store the role-id and secret-id + // We will need this if SMS restarts + <span class="cov5" title="7">smsauth.WriteToFile(v.roleID, "auth/role") + smsauth.WriteToFile(v.secretID, "auth/secret") + + return nil</span> +} + +// Function checkToken() gets called multiple times to create +// temporary tokens +func (v *Vault) checkToken() error <span class="cov9" title="54">{ + + v.Lock() + defer v.Unlock() + + // Init Role if it is not yet done + // Role needs to be created before token can be created + err := v.initRole() + if err != nil </span><span class="cov0" title="0">{ + smslogger.WriteError(err.Error()) + return errors.New("Unable to initRole in checkToken") + }</span> + + // Return immediately if token still has life + <span class="cov9" title="54">if v.vaultClient.Token() != "" && + time.Since(v.vaultTempTokenTTL) < time.Minute*50 </span><span class="cov9" title="47">{ + return nil + }</span> + + // Create a temporary token using our roleID and secretID + <span class="cov5" title="7">out, err := v.vaultClient.Logical().Write("auth/approle/login", + map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID}) + if smslogger.CheckError(err, "Create Temp Token") != nil </span><span class="cov0" title="0">{ + return errors.New("Unable to create Temporary Token for Role") + }</span> + + <span class="cov5" title="7">tok, err := out.TokenID() + + v.vaultTempTokenTTL = time.Now() + v.vaultClient.SetToken(tok) + return nil</span> +} + +// vaultInit() is used to initialize the vault in cases where it is not +// initialized. This happens once during intial bring up. +func (v *Vault) initializeVault() error <span class="cov2" title="2">{ + + // Check for vault init status and don't exit till it is initialized + for </span><span class="cov2" title="2">{ + init, err := v.vaultClient.Sys().InitStatus() + if smslogger.CheckError(err, "Get Vault Init Status") != nil </span><span class="cov0" title="0">{ + smslogger.WriteInfo("Trying again in 10s...") + time.Sleep(time.Second * 10) + continue</span> + } + // Did not get any error + <span class="cov2" title="2">if init == true </span><span class="cov1" title="1">{ + smslogger.WriteInfo("Vault is already Initialized") + return nil + }</span> + + // init status is false + // break out of loop and finish initialization + <span class="cov1" title="1">smslogger.WriteInfo("Vault is not initialized. Initializing...") + break</span> + } + + // Hardcoded this to 3. We should make this configurable + // in the future + <span class="cov1" title="1">initReq := &vaultapi.InitRequest{ + SecretShares: 3, + SecretThreshold: 3, + } + + pbkey, prkey, err := smsauth.GeneratePGPKeyPair() + + if smslogger.CheckError(err, "Generating PGP Keys") != nil </span><span class="cov0" title="0">{ + smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!") + }</span><span class="cov1" title="1"> else { + initReq.PGPKeys = []string{pbkey, pbkey, pbkey} + initReq.RootTokenPGPKey = pbkey + }</span> + + <span class="cov1" title="1">resp, err := v.vaultClient.Sys().Init(initReq) + if smslogger.CheckError(err, "Initialize Vault") != nil </span><span class="cov0" title="0">{ + return errors.New("FATAL: Unable to initialize Vault") + }</span> + + <span class="cov1" title="1">if resp != nil </span><span class="cov1" title="1">{ + v.prkey = prkey + v.shards = resp.KeysB64 + v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey) + return nil + }</span> + + <span class="cov0" title="0">return errors.New("FATAL: Init response was empty")</span> +} +</pre> + + <pre class="file" id="file3" style="display: none">/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "encoding/json" + "os" + smslogger "sms/log" +) + +// SMSConfiguration loads up all the values that are used to configure +// backend implementations +// TODO: Review these and see if they can be created/discovered dynamically +type SMSConfiguration struct { + CAFile string `json:"cafile"` + ServerCert string `json:"servercert"` + ServerKey string `json:"serverkey"` + + BackendAddress string `json:"smsdbaddress"` + VaultToken string `json:"vaulttoken"` + DisableTLS bool `json:"disable_tls"` + BackendAddressEnvVariable string `json:"smsdburlenv"` +} + +// SMSConfig is the structure that stores the configuration +var SMSConfig *SMSConfiguration + +// ReadConfigFile reads the specified smsConfig file to setup some env variables +func ReadConfigFile(file string) (*SMSConfiguration, error) <span class="cov10" title="3">{ + if SMSConfig == nil </span><span class="cov10" title="3">{ + f, err := os.Open(file) + if err != nil </span><span class="cov1" title="1">{ + return nil, err + }</span> + <span class="cov6" title="2">defer f.Close() + + // Default behaviour is to enable TLS + SMSConfig = &SMSConfiguration{DisableTLS: false} + decoder := json.NewDecoder(f) + err = decoder.Decode(SMSConfig) + if err != nil </span><span class="cov0" title="0">{ + return nil, err + }</span> + + <span class="cov6" title="2">if SMSConfig.BackendAddress == "" && SMSConfig.BackendAddressEnvVariable != "" </span><span class="cov0" title="0">{ + // Get the value from ENV variable + smslogger.WriteInfo("Using Environment Variable: " + SMSConfig.BackendAddressEnvVariable) + SMSConfig.BackendAddress = os.Getenv(SMSConfig.BackendAddressEnvVariable) + }</span> + } + + <span class="cov6" title="2">return SMSConfig, nil</span> +} +</pre> + + <pre class="file" id="file4" style="display: none">/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package handler + +import ( + "encoding/json" + "github.com/gorilla/mux" + "net/http" + + smsbackend "sms/backend" + smslogger "sms/log" +) + +// handler stores two interface implementations that implement +// the backend functionality +type handler struct { + secretBackend smsbackend.SecretBackend + loginBackend smsbackend.LoginBackend +} + +// createSecretDomainHandler creates a secret domain with a name provided +func (h handler) createSecretDomainHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{ + var d smsbackend.SecretDomain + + err := json.NewDecoder(r.Body).Decode(&d) + if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusBadRequest) + return + }</span> + + <span class="cov6" title="3">dom, err := h.secretBackend.CreateSecretDomain(d.Name) + if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + <span class="cov6" title="3">w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(dom) + if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> +} + +// deleteSecretDomainHandler deletes a secret domain with the name provided +func (h handler) deleteSecretDomainHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{ + vars := mux.Vars(r) + domName := vars["domName"] + + err := h.secretBackend.DeleteSecretDomain(domName) + if smslogger.CheckError(err, "DeleteSecretDomainHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + <span class="cov6" title="3">w.WriteHeader(http.StatusNoContent)</span> +} + +// createSecretHandler handles creation of secrets on a given domain name +func (h handler) createSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{ + // Get domain name from URL + vars := mux.Vars(r) + domName := vars["domName"] + + // Get secrets to be stored from body + var b smsbackend.Secret + err := json.NewDecoder(r.Body).Decode(&b) + if smslogger.CheckError(err, "CreateSecretHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusBadRequest) + return + }</span> + + <span class="cov10" title="7">err = h.secretBackend.CreateSecret(domName, b) + if smslogger.CheckError(err, "CreateSecretHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + <span class="cov10" title="7">w.WriteHeader(http.StatusCreated)</span> +} + +// getSecretHandler handles reading a secret by given domain name and secret name +func (h handler) getSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{ + vars := mux.Vars(r) + domName := vars["domName"] + secName := vars["secretName"] + + sec, err := h.secretBackend.GetSecret(domName, secName) + if smslogger.CheckError(err, "GetSecretHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + <span class="cov10" title="7">w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(sec) + if smslogger.CheckError(err, "GetSecretHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> +} + +// listSecretHandler handles listing all secrets under a particular domain name +func (h handler) listSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{ + vars := mux.Vars(r) + domName := vars["domName"] + + secList, err := h.secretBackend.ListSecret(domName) + if smslogger.CheckError(err, "ListSecretHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + // Creating an anonymous struct to store the returned list of data + <span class="cov6" title="3">var retStruct = struct { + SecretNames []string `json:"secretnames"` + }{ + secList, + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(retStruct) + if smslogger.CheckError(err, "ListSecretHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> +} + +// deleteSecretHandler handles deleting a secret by given domain name and secret name +func (h handler) deleteSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{ + vars := mux.Vars(r) + domName := vars["domName"] + secName := vars["secretName"] + + err := h.secretBackend.DeleteSecret(domName, secName) + if smslogger.CheckError(err, "DeleteSecretHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + <span class="cov10" title="7">w.WriteHeader(http.StatusNoContent)</span> +} + +// statusHandler returns information related to SMS and SMS backend services +func (h handler) statusHandler(w http.ResponseWriter, r *http.Request) <span class="cov7" title="4">{ + s, err := h.secretBackend.GetStatus() + if smslogger.CheckError(err, "StatusHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + <span class="cov7" title="4">status := struct { + Seal bool `json:"sealstatus"` + }{ + s, + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(status) + if smslogger.CheckError(err, "StatusHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> +} + +// loginHandler handles login via password and username +func (h handler) loginHandler(w http.ResponseWriter, r *http.Request) {<span class="cov0" title="0"> + +}</span> + +// unsealHandler is a pass through that sends requests from quorum client +// to the backend. +func (h handler) unsealHandler(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{ + // Get shards to be used for unseal + type unsealStruct struct { + UnsealShard string `json:"unsealshard"` + } + + var inp unsealStruct + decoder := json.NewDecoder(r.Body) + decoder.DisallowUnknownFields() + err := decoder.Decode(&inp) + if smslogger.CheckError(err, "UnsealHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, "Bad input JSON", http.StatusBadRequest) + return + }</span> + + <span class="cov0" title="0">err = h.secretBackend.Unseal(inp.UnsealShard) + if smslogger.CheckError(err, "UnsealHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> +} + +// registerHandler allows the quorum clients to register with SMS +// with their PGP public keys that are then used by sms for backend +// initialization +func (h handler) registerHandler(w http.ResponseWriter, r *http.Request) <span class="cov1" title="1">{ + // Get shards to be used for unseal + type registerStruct struct { + PGPKey string `json:"pgpkey"` + QuorumID string `json:"quorumid"` + } + + var inp registerStruct + decoder := json.NewDecoder(r.Body) + decoder.DisallowUnknownFields() + err := decoder.Decode(&inp) + if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, "Bad input JSON", http.StatusBadRequest) + return + }</span> + + <span class="cov1" title="1">sh, err := h.secretBackend.RegisterQuorum(inp.PGPKey) + if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> + + // Creating a struct for return data + <span class="cov1" title="1">shStruct := struct { + Shard string `json:"shard"` + }{ + sh, + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(shStruct) + if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{ + http.Error(w, err.Error(), http.StatusInternalServerError) + return + }</span> +} + +// CreateRouter returns an http.Handler for the registered URLs +// Takes an interface implementation as input +func CreateRouter(b smsbackend.SecretBackend) http.Handler <span class="cov4" title="2">{ + h := handler{secretBackend: b} + + // Create a new mux to handle URL endpoints + router := mux.NewRouter() + + router.HandleFunc("/v1/sms/login", h.loginHandler).Methods("POST") + + // Initialization APIs which will be used by quorum client + // to unseal and to provide root token to sms service + router.HandleFunc("/v1/sms/quorum/status", h.statusHandler).Methods("GET") + router.HandleFunc("/v1/sms/quorum/unseal", h.unsealHandler).Methods("POST") + router.HandleFunc("/v1/sms/quorum/register", h.registerHandler).Methods("POST") + + router.HandleFunc("/v1/sms/domain", h.createSecretDomainHandler).Methods("POST") + router.HandleFunc("/v1/sms/domain/{domName}", h.deleteSecretDomainHandler).Methods("DELETE") + + router.HandleFunc("/v1/sms/domain/{domName}/secret", h.createSecretHandler).Methods("POST") + router.HandleFunc("/v1/sms/domain/{domName}/secret", h.listSecretHandler).Methods("GET") + router.HandleFunc("/v1/sms/domain/{domName}/secret/{secretName}", h.getSecretHandler).Methods("GET") + router.HandleFunc("/v1/sms/domain/{domName}/secret/{secretName}", h.deleteSecretHandler).Methods("DELETE") + + return router +}</span> +</pre> + + <pre class="file" id="file5" style="display: none">/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package log + +import ( + "fmt" + "log" + "os" +) + +var errL, warnL, infoL *log.Logger +var stdErr, stdWarn, stdInfo *log.Logger + +// Init will be called by sms.go before any other packages use it +func Init(filePath string) <span class="cov1" title="1">{ + + stdErr = log.New(os.Stderr, "ERROR: ", log.Lshortfile|log.LstdFlags) + stdWarn = log.New(os.Stdout, "WARNING: ", log.Lshortfile|log.LstdFlags) + stdInfo = log.New(os.Stdout, "INFO: ", log.Lshortfile|log.LstdFlags) + + if filePath == "" </span><span class="cov0" title="0">{ + // We will just to std streams + return + }</span> + + <span class="cov1" title="1">f, err := os.Create(filePath) + if err != nil </span><span class="cov0" title="0">{ + stdErr.Println("Unable to create log file: " + err.Error()) + return + }</span> + + <span class="cov1" title="1">errL = log.New(f, "ERROR: ", log.Lshortfile|log.LstdFlags) + warnL = log.New(f, "WARNING: ", log.Lshortfile|log.LstdFlags) + infoL = log.New(f, "INFO: ", log.Lshortfile|log.LstdFlags)</span> +} + +// WriteError writes output to the writer we have +// defined during its creation with ERROR prefix +func WriteError(msg string) <span class="cov0" title="0">{ + if errL != nil </span><span class="cov0" title="0">{ + errL.Output(2, fmt.Sprintln(msg)) + }</span> + <span class="cov0" title="0">if stdErr != nil </span><span class="cov0" title="0">{ + stdErr.Output(2, fmt.Sprintln(msg)) + }</span> +} + +// WriteWarn writes output to the writer we have +// defined during its creation with WARNING prefix +func WriteWarn(msg string) <span class="cov0" title="0">{ + if warnL != nil </span><span class="cov0" title="0">{ + warnL.Output(2, fmt.Sprintln(msg)) + }</span> + <span class="cov0" title="0">if stdWarn != nil </span><span class="cov0" title="0">{ + stdWarn.Output(2, fmt.Sprintln(msg)) + }</span> +} + +// WriteInfo writes output to the writer we have +// defined during its creation with INFO prefix +func WriteInfo(msg string) <span class="cov1" title="1">{ + if infoL != nil </span><span class="cov1" title="1">{ + infoL.Output(2, fmt.Sprintln(msg)) + }</span> + <span class="cov1" title="1">if stdInfo != nil </span><span class="cov1" title="1">{ + stdInfo.Output(2, fmt.Sprintln(msg)) + }</span> +} + +//CheckError is a helper function to reduce +//repitition of error checkign blocks of code +func CheckError(err error, topic string) error <span class="cov10" title="116">{ + if err != nil </span><span class="cov1" title="1">{ + msg := topic + ": " + err.Error() + if errL != nil </span><span class="cov1" title="1">{ + errL.Output(2, fmt.Sprintln(msg)) + }</span> + <span class="cov1" title="1">if stdErr != nil </span><span class="cov1" title="1">{ + stdErr.Output(2, fmt.Sprintln(msg)) + }</span> + <span class="cov1" title="1">return err</span> + } + <span class="cov9" title="115">return nil</span> +} +</pre> + + <pre class="file" id="file6" style="display: none">/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + + smsauth "sms/auth" + smsbackend "sms/backend" + smsconfig "sms/config" + smshandler "sms/handler" + smslogger "sms/log" +) + +func main() <span class="cov8" title="1">{ + // Initialize logger + smslogger.Init("sms.log") + + // Read Configuration File + smsConf, err := smsconfig.ReadConfigFile("smsconfig.json") + if err != nil </span><span class="cov0" title="0">{ + log.Fatal(err) + }</span> + + <span class="cov8" title="1">backendImpl, err := smsbackend.InitSecretBackend() + if err != nil </span><span class="cov0" title="0">{ + log.Fatal(err) + }</span> + + <span class="cov8" title="1">httpRouter := smshandler.CreateRouter(backendImpl) + + httpServer := &http.Server{ + Handler: httpRouter, + Addr: ":10443", + } + + // Listener for SIGINT so that it returns cleanly + connectionsClose := make(chan struct{}) + go func() </span><span class="cov8" title="1">{ + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + httpServer.Shutdown(context.Background()) + close(connectionsClose) + }</span>() + + // Start in TLS mode by default + <span class="cov8" title="1">if smsConf.DisableTLS == true </span><span class="cov0" title="0">{ + smslogger.WriteWarn("TLS is Disabled") + err = httpServer.ListenAndServe() + }</span><span class="cov8" title="1"> else { + // TODO: Use CA certificate from AAF + tlsConfig, err := smsauth.GetTLSConfig(smsConf.CAFile) + if err != nil </span><span class="cov0" title="0">{ + log.Fatal(err) + }</span> + + <span class="cov8" title="1">httpServer.TLSConfig = tlsConfig + err = httpServer.ListenAndServeTLS(smsConf.ServerCert, smsConf.ServerKey)</span> + } + + <span class="cov8" title="1">if err != nil && err != http.ErrServerClosed </span><span class="cov0" title="0">{ + log.Fatal(err) + }</span> + + <span class="cov8" title="1"><-connectionsClose</span> +} +</pre> + + </div> + </body> + <script> + (function() { + var files = document.getElementById('files'); + var visible; + files.addEventListener('change', onChange, false); + function select(part) { + if (visible) + visible.style.display = 'none'; + visible = document.getElementById(part); + if (!visible) + return; + files.value = part; + visible.style.display = 'block'; + location.hash = part; + } + function onChange() { + select(files.value); + window.scrollTo(0, 0); + } + if (location.hash != "") { + select(location.hash.substr(1)); + } + if (!visible) { + select("file0"); + } + })(); + </script> +</html> diff --git a/docs/coverage.md b/docs/coverage.md new file mode 100644 index 0000000..6168342 --- /dev/null +++ b/docs/coverage.md @@ -0,0 +1,41 @@ +## Code Coverage Reports for Golang Applications ## + +This document covers how to generate HTML Code Coverage Reports for +Golang Applications. + +#### Generate a test executable which calls your main() + +```sh +$ go test -c -covermode=count -coverpkg ./... +``` + +#### Run the generated application to produce a new coverage report + +```sh +$ ./sms.test -test.run "^TestMain$" -test.coverprofile=coverage.cov +``` + +#### Run your unit tests to produce their coverage report + +```sh +$ go test -test.covermode=count -test.coverprofile=unit.out ./... +``` + +#### Merge the two coverage Reports + +```sh +$ go get github.com/wadey/gocovmerge +$ gocovmerge unit.out coverage.cov > all.out +``` + +#### Generate HTML Report + +```sh +$ go tool cover -html all.out -o coverage.html +``` + +#### Generate Function Report + +```sh +$ go tool cover -func all.out +```
\ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..5f17a04 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,37 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. http://creativecommons.org/licenses/by/4.0 +.. Copyright 2018 Intel Corporation, Inc + +SMS-Secret Management Service +================================== + +.. toctree:: + :maxdepth: 1 + + installation + usage + apiswagger + + +Introduction +------------ + +This project aims at the Storage of sensitive information such as passwords. + +**Current state and gaps** + +Many services in ONAP use password based authentication. Eg: Database servers, publish/subscribe brokers etc. +Passwords are stored in plain text files in many services. +With multiple instances of these services, the attach surface area becomes very big. +Hence there is a need to ensure that attack surface related to password exposure is reduced. + +**Requirement:** + +Need for secure secret management. Services are expected to get the secret only on needed basis using secret reference and remove the secrets once they are used up. + +**Secret Service High Level Flow Diagram** + +.. image:: sms_high_level.png + :width: 4555550px + :height: 300px + :alt: SMS Flow Diagram diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..b22d133 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,33 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. http://creativecommons.org/licenses/by/4.0 +.. Copyright 2018 Intel Corporation, Inc + +Installation +============ + +The Secret Managment Project is a subproject of AAF and will deployed via Helm on Kubernetes +under the OOM Project + +.. code-block:: console + + # Set Datastore as Consul + DATASTORE="consul" + # Set IP address of where Consul is running + DATASTORE_IP="localhost" + # Set mountpath inside the container where persistent data is stored. + MOUNTPATH="/dkv_mount_path/configs/" + # Place all Config data which needs to be loaded in default directory. + DEFAULT_CONFIGS=$(pwd)/mountpath/default + # Create the directories. + mkdir -p mountpath/default + # Login to Nexus. + docker login -u docker -p docker nexus3.onap.org:10001 + # Pull distributed-kv-store image. + docker pull nexus3.onap.org:10001/onap/music/distributed-kv-store + # Run the distributed-kv-store image. + docker run -e DATASTORE=$DATASTORE -e DATASTORE_IP=$DATASTORE_IP -e MOUNTPATH=$MOUNTPATH -d \ + --name dkv \ + -v $DEFAULT_CONFIGS:/dkv_mount_path/configs/default \ + -p 8200:8200 -p 8080:8080 nexus3.onap.org:10001/onap/music/distributed-kv-store + +.. end diff --git a/docs/sms_high_level.png b/docs/sms_high_level.png Binary files differnew file mode 100644 index 0000000..3cd14ba --- /dev/null +++ b/docs/sms_high_level.png diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..b35e9b5 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,54 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. http://creativecommons.org/licenses/by/4.0 +.. Copyright 2018 Intel Corporation, Inc + +Typical Usage Scenario +====================== + +.. code-block:: guess + + ## Create a Domain + ## This is where all your secrets will be stored + curl -H "Accept: application/json" --cacert ca.pem --cert client.cert --key client.key + -X POST \ + -d '{ + "name": "mysecretdomain" + }' + https://sms:10443/v1/sms/domain + + ## Add a new Secret + curl -H "Accept: application/json" --cacert ca.pem --cert client.cert --key client.key + -X POST \ + -d '{ + "name": "mysecret", + "values": { + "name": "rah", + "age": 35, + "password": "mypassword" + } + }' + https://sms:10443/v1/sms/domain/<domaincurltestdomain/secret + + + ## List all Secrets under a Domain + curl -H "Accept: application/json" --cacert ca.pem --cert client.cert --key client.key + -X GET \ + https://sms:10443/v1/sms/domain/curltestdomain/secret + + ## Get a Secret in a Domain + curl -H "Accept: application/json" --cacert ca.pem --cert client.cert --key client.key + -X GET \ + https://sms:10443/v1/sms/domain/curltestdomain/secret/curltestsecret1 + + ## Delete a Secret in specified Domain + curl -H "Accept: application/json" --cacert ca.pem --cert client.cert --key client.key + -X DELETE \ + https://sms:10443/v1/sms/domain/curltestdomain/secret/curltestsecret1 + + ## Delete a Domain + ## This will delete all the secrets in that Domain + curl -H "Accept: application/json" --cacert ca.pem --cert client.cert --key client.key + -X DELETE \ + https://sms:10443/v1/sms/domain/curltestdomain + +.. end |