<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Open Infrastructure Services LLC – Technical Posts</title><link>http://openinfrastructure.co/blog/technical/</link><description>Recent content in Technical Posts on Open Infrastructure Services LLC</description><generator>Hugo -- gohugo.io</generator><atom:link href="http://openinfrastructure.co/blog/technical/index.xml" rel="self" type="application/rss+xml"/><item><title>Blog: FastAPI/Starlette OIDC Authentication</title><link>http://openinfrastructure.co/blog/2023/03/28/fastapi/starlette-oidc-authentication/</link><pubDate>Tue, 28 Mar 2023 12:00:00 +0000</pubDate><guid>http://openinfrastructure.co/blog/2023/03/28/fastapi/starlette-oidc-authentication/</guid><description>
&lt;p>This post demonstrates how to validate a bearer token issued by an oidc provider. Assume the oauth flow is handled downstream. Starlette&amp;rsquo;s AuthenticationMiddleware is used to attach a SimpleUser and AuthCredentials instance to each request. Authlib validates the iss, exp, and aud claims. Starlette&amp;rsquo;s require decorator authorizes access token scopes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#8a8a8a;background-color:#1c1c1c;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4e4e4e"># main.py&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> fastapi &lt;span style="color:#d75f00">import&lt;/span> FastAPI, Request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> .middleware &lt;span style="color:#d75f00">import&lt;/span> middleware
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> starlette.authentication &lt;span style="color:#d75f00">import&lt;/span> requires
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> starlette.requests &lt;span style="color:#d75f00">import&lt;/span> Request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app = FastAPI(middleware=middleware)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0087ff">@app.get&lt;/span>(&lt;span style="color:#00afaf">&amp;#34;/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#5f8700">async&lt;/span> &lt;span style="color:#5f8700">def&lt;/span> &lt;span style="color:#0087ff">homepage&lt;/span>(request: Request):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">if&lt;/span> request.user.is_authenticated:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">return&lt;/span> {&lt;span style="color:#00afaf">&amp;#34;username&amp;#34;&lt;/span>: request.user.username, &lt;span style="color:#00afaf">&amp;#34;anonymous&amp;#34;&lt;/span>: &lt;span style="color:#d75f00">False&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">return&lt;/span> {&lt;span style="color:#00afaf">&amp;#34;anonymous&amp;#34;&lt;/span>: &lt;span style="color:#d75f00">True&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0087ff">@app.get&lt;/span>(&lt;span style="color:#00afaf">&amp;#34;/profile/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0087ff">@requires&lt;/span>(scopes=[&lt;span style="color:#00afaf">&amp;#34;profile&amp;#34;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#5f8700">async&lt;/span> &lt;span style="color:#5f8700">def&lt;/span> &lt;span style="color:#0087ff">profile&lt;/span>(request: Request):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">return&lt;/span> {&lt;span style="color:#00afaf">&amp;#34;user&amp;#34;&lt;/span>: request.user, &lt;span style="color:#00afaf">&amp;#34;auth&amp;#34;&lt;/span>: request.auth}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#8a8a8a;background-color:#1c1c1c;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#4e4e4e"># middleware.py&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">import&lt;/span> logging
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> functools &lt;span style="color:#d75f00">import&lt;/span> lru_cache
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> threading &lt;span style="color:#d75f00">import&lt;/span> Timer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> urllib.request &lt;span style="color:#d75f00">import&lt;/span> urlopen
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> authlib.jose.rfc7517.jwk &lt;span style="color:#d75f00">import&lt;/span> JsonWebKey, KeySet
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> authlib.oauth2 &lt;span style="color:#d75f00">import&lt;/span> OAuth2Error, ResourceProtector
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> authlib.oauth2.rfc6749 &lt;span style="color:#d75f00">import&lt;/span> MissingAuthorizationError
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> authlib.oauth2.rfc7523 &lt;span style="color:#d75f00">import&lt;/span> JWTBearerTokenValidator
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> authlib.oauth2.rfc7523.validator &lt;span style="color:#d75f00">import&lt;/span> JWTBearerToken
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> fastapi &lt;span style="color:#d75f00">import&lt;/span> Request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> pydantic &lt;span style="color:#d75f00">import&lt;/span> BaseSettings, Field
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> starlette.authentication &lt;span style="color:#d75f00">import&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthCredentials,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthenticationBackend,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthenticationError,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SimpleUser,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> starlette.middleware &lt;span style="color:#d75f00">import&lt;/span> Middleware
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> starlette.middleware.authentication &lt;span style="color:#d75f00">import&lt;/span> AuthenticationMiddleware
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d75f00">from&lt;/span> starlette.requests &lt;span style="color:#d75f00">import&lt;/span> HTTPConnection, Request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>logger = logging.getLogger(__name__)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#5f8700">class&lt;/span> &lt;span style="color:#0087ff">Settings&lt;/span>(BaseSettings):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> secret_key: &lt;span style="color:#0087ff">str&lt;/span> = Field(title=&lt;span style="color:#00afaf">&amp;#34;Starlette SessionMiddleware Secret Key&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> oidc_issuer: &lt;span style="color:#0087ff">str&lt;/span> = Field(title=&lt;span style="color:#00afaf">&amp;#34;OIDC Issuer&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> oidc_audience: &lt;span style="color:#0087ff">str&lt;/span> = Field(title=&lt;span style="color:#00afaf">&amp;#34;OIDC Audience&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">class&lt;/span> &lt;span style="color:#0087ff">Config&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> env_file = &lt;span style="color:#00afaf">&amp;#34;.env&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>settings = Settings()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#5f8700">class&lt;/span> &lt;span style="color:#0087ff">RepeatTimer&lt;/span>(Timer):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">def&lt;/span> __init__(&lt;span style="color:#0087ff">self&lt;/span>, *args, **kwargs) -&amp;gt; &lt;span style="color:#d75f00">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">super&lt;/span>().__init__(*args, **kwargs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>.daemon = &lt;span style="color:#d75f00">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">def&lt;/span> &lt;span style="color:#0087ff">run&lt;/span>(&lt;span style="color:#0087ff">self&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">while&lt;/span> &lt;span style="color:#5f8700">not&lt;/span> &lt;span style="color:#0087ff">self&lt;/span>.finished.wait(&lt;span style="color:#0087ff">self&lt;/span>.interval):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>.function(*&lt;span style="color:#0087ff">self&lt;/span>.args, **&lt;span style="color:#0087ff">self&lt;/span>.kwargs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#5f8700">class&lt;/span> &lt;span style="color:#0087ff">BearerTokenValidator&lt;/span>(JWTBearerTokenValidator):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">def&lt;/span> __init__(&lt;span style="color:#0087ff">self&lt;/span>, issuer: &lt;span style="color:#0087ff">str&lt;/span>, audience: &lt;span style="color:#0087ff">str&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>._issuer = issuer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>._jwks_uri: &lt;span style="color:#0087ff">str&lt;/span> | &lt;span style="color:#d75f00">None&lt;/span> = &lt;span style="color:#d75f00">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">super&lt;/span>().__init__(public_key=&lt;span style="color:#0087ff">self&lt;/span>.fetch_key(), issuer=issuer)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>.claims_options = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00afaf">&amp;#34;exp&amp;#34;&lt;/span>: {&lt;span style="color:#00afaf">&amp;#34;essential&amp;#34;&lt;/span>: &lt;span style="color:#d75f00">True&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00afaf">&amp;#34;aud&amp;#34;&lt;/span>: {&lt;span style="color:#00afaf">&amp;#34;essential&amp;#34;&lt;/span>: &lt;span style="color:#d75f00">True&lt;/span>, &lt;span style="color:#00afaf">&amp;#34;value&amp;#34;&lt;/span>: audience},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00afaf">&amp;#34;iss&amp;#34;&lt;/span>: {&lt;span style="color:#00afaf">&amp;#34;essential&amp;#34;&lt;/span>: &lt;span style="color:#d75f00">True&lt;/span>, &lt;span style="color:#00afaf">&amp;#34;value&amp;#34;&lt;/span>: issuer},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>._timer = RepeatTimer(&lt;span style="color:#00afaf">3600&lt;/span>, &lt;span style="color:#0087ff">self&lt;/span>.refresh)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>._timer.start()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">def&lt;/span> &lt;span style="color:#0087ff">refresh&lt;/span>(&lt;span style="color:#0087ff">self&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>.public_key = &lt;span style="color:#0087ff">self&lt;/span>.fetch_key()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">except&lt;/span> &lt;span style="color:#af8700">Exception&lt;/span> &lt;span style="color:#5f8700">as&lt;/span> exc:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.warning(&lt;span style="color:#00afaf">f&lt;/span>&lt;span style="color:#00afaf">&amp;#34;Could not update jwks public key: &lt;/span>&lt;span style="color:#00afaf">{&lt;/span>exc&lt;span style="color:#00afaf">}&lt;/span>&lt;span style="color:#00afaf">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">def&lt;/span> &lt;span style="color:#0087ff">fetch_key&lt;/span>(&lt;span style="color:#0087ff">self&lt;/span>) -&amp;gt; KeySet:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00afaf">&amp;#34;&amp;#34;&amp;#34;Fetch the jwks_uri document and return the KeySet.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response = urlopen(&lt;span style="color:#0087ff">self&lt;/span>.jwks_uri)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logger.debug(&lt;span style="color:#00afaf">f&lt;/span>&lt;span style="color:#00afaf">&amp;#34;OK GET &lt;/span>&lt;span style="color:#00afaf">{&lt;/span>&lt;span style="color:#0087ff">self&lt;/span>.jwks_uri&lt;span style="color:#00afaf">}&lt;/span>&lt;span style="color:#00afaf">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">return&lt;/span> JsonWebKey.import_key_set(json.loads(response.read()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">def&lt;/span> &lt;span style="color:#0087ff">jwks_uri&lt;/span>(&lt;span style="color:#0087ff">self&lt;/span>) -&amp;gt; &lt;span style="color:#0087ff">str&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00afaf">&amp;#34;&amp;#34;&amp;#34;The jwks_uri field of the openid-configuration document.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">if&lt;/span> &lt;span style="color:#0087ff">self&lt;/span>._jwks_uri &lt;span style="color:#5f8700">is&lt;/span> &lt;span style="color:#d75f00">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config_url = urlopen(&lt;span style="color:#00afaf">f&lt;/span>&lt;span style="color:#00afaf">&amp;#34;&lt;/span>&lt;span style="color:#00afaf">{&lt;/span>&lt;span style="color:#0087ff">self&lt;/span>._issuer&lt;span style="color:#00afaf">}&lt;/span>&lt;span style="color:#00afaf">/.well-known/openid-configuration&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config = json.loads(config_url.read())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>._jwks_uri = config[&lt;span style="color:#00afaf">&amp;#34;jwks_uri&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">return&lt;/span> &lt;span style="color:#0087ff">self&lt;/span>._jwks_uri
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#5f8700">class&lt;/span> &lt;span style="color:#0087ff">BearerTokenAuthBackend&lt;/span>(AuthenticationBackend):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">def&lt;/span> __init__(&lt;span style="color:#0087ff">self&lt;/span>, issuer: &lt;span style="color:#0087ff">str&lt;/span>, audience: &lt;span style="color:#0087ff">str&lt;/span>) -&amp;gt; &lt;span style="color:#d75f00">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rp = ResourceProtector()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> validator = BearerTokenValidator(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> issuer=issuer,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audience=audience,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rp.register_token_validator(validator)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0087ff">self&lt;/span>.resource_protector = rp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">async&lt;/span> &lt;span style="color:#5f8700">def&lt;/span> &lt;span style="color:#0087ff">authenticate&lt;/span>(&lt;span style="color:#0087ff">self&lt;/span>, conn: HTTPConnection):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> request = Request(conn.scope)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> token: JWTBearerToken = &lt;span style="color:#0087ff">self&lt;/span>.resource_protector.validate_request(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scopes=[&lt;span style="color:#00afaf">&amp;#34;openid&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> request=request,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">except&lt;/span> (MissingAuthorizationError, OAuth2Error) &lt;span style="color:#5f8700">as&lt;/span> error:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">raise&lt;/span> AuthenticationError(error.description) &lt;span style="color:#d75f00">from&lt;/span> error
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scope: &lt;span style="color:#0087ff">str&lt;/span> = token.get_scope()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scopes = scope.split()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scopes.append(&lt;span style="color:#00afaf">&amp;#34;authenticated&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#5f8700">return&lt;/span> AuthCredentials(scopes=scopes), SimpleUser(username=token[&lt;span style="color:#00afaf">&amp;#34;email&amp;#34;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>middleware = [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Middleware(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AuthenticationMiddleware,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> backend=BearerTokenAuthBackend(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> issuer=settings.oidc_issuer,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> audience=settings.oidc_audience,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Blog: Update ESXi 6.5 to U1 over SSH</title><link>http://openinfrastructure.co/blog/2017/11/22/update-esxi-6.5-to-u1-over-ssh/</link><pubDate>Wed, 22 Nov 2017 17:18:00 +0000</pubDate><guid>http://openinfrastructure.co/blog/2017/11/22/update-esxi-6.5-to-u1-over-ssh/</guid><description>
&lt;p>Updating ESXi 6.5 to 6.5 U1, I encountered the following error:&lt;/p>
&lt;pre tabindex="0">&lt;code>[root@esx:~] esxcli software profile update -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml -p ESXi-6.5.0-20170702001-standard
[InstallationError]
[Errno 28] No space left on device
vibs = VMware_locker_tools-light_6.5.0-0.23.5969300
Please refer to the log file for more details.
&lt;/code>&lt;/pre>&lt;p>The solution to this problem is to enable swap. I&amp;rsquo;m running this ESXi host on
a single 32GB USB Thumb Drive, so I first had to create a VMFS5 datastore using
the process at &lt;a href="http://openinfrastructure.co/news/esxi-single-usb-boot-plus-datastore/">ESXi 6.5 Single USB Thumb
Drive&lt;/a>.&lt;/p>
&lt;p>Once a datastore exists, enable Swap. Go to Host &amp;gt; System &amp;gt; Swap and activate
swap on your datastore of choice. In my case there&amp;rsquo;s only one.&lt;/p>
&lt;figure class="card rounded p-2 td-post-card mb-4 mt-4" style="max-width: 810px">
&lt;img class="card-img-top" src="http://openinfrastructure.co/blog/2017/11/22/update-esxi-6.5-to-u1-over-ssh/swap_hu95ddb8c366e87f3041a5bc6e091f731f_329680_800x0_resize_catmullrom_3.png" width="800" height="595">
&lt;figcaption class="card-body px-0 pt-2 pb-0">
&lt;p class="card-text">
&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Once activated, this process to update ESXi over SSH worked flawlessly:&lt;/p>
&lt;p>Enable outbound HTTP connections:&lt;/p>
&lt;pre>&lt;code>esxcli network firewall ruleset set -e true -r httpClient
&lt;/code>&lt;/pre>
&lt;p>Perform the update:&lt;/p>
&lt;pre>&lt;code>esxcli software profile update -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml -p ESXi-6.5.0-20170702001-standard
&lt;/code>&lt;/pre>
&lt;p>Lock down HTTP connections after the update:&lt;/p>
&lt;pre>&lt;code>esxcli network firewall ruleset set -e false -r httpClient
&lt;/code>&lt;/pre>
&lt;p>Reboot the host:&lt;/p>
&lt;pre>&lt;code>reboot
&lt;/code>&lt;/pre>
&lt;p>For future reference, to see a list of available updates, use&lt;/p>
&lt;pre>&lt;code>esxcli software sources profile list -d \
https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml \
| awk '/6.5.0/ {print $1}'
&lt;/code>&lt;/pre>
&lt;pre tabindex="0">&lt;code>[root@esx:~] esxcli software sources profile list -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml | awk &amp;#39;/6.5.0/ {print $1}&amp;#39;
ESXi-6.5.0-20170701001s-no-tools
ESXi-6.5.0-20170404001-standard
ESXi-6.5.0-4564106-standard
ESXi-6.5.0-20170104001-standard
ESXi-6.5.0-20171004001-no-tools
ESXi-6.5.0-20170702001-no-tools
ESXi-6.5.0-20170404001-no-tools
ESXi-6.5.0-20170304101-no-tools
ESXi-6.5.0-20171004001-standard
ESXi-6.5.0-20170104001-no-tools
ESXi-6.5.0-4564106-no-tools
ESXi-6.5.0-20170304101-standard
ESXi-6.5.0-20170301001s-standard
ESXi-6.5.0-20170701001s-standard
ESXi-6.5.0-20170304001-standard
ESXi-6.5.0-20170702001-standard
ESXi-6.5.0-20170304001-no-tools
ESXi-6.5.0-20170301001s-no-tools
&lt;/code>&lt;/pre></description></item><item><title>Blog: ESXi 6.5 Single USB Thumb Drive</title><link>http://openinfrastructure.co/blog/2017/11/22/esxi-6.5-single-usb-thumb-drive/</link><pubDate>Wed, 22 Nov 2017 15:06:00 +0000</pubDate><guid>http://openinfrastructure.co/blog/2017/11/22/esxi-6.5-single-usb-thumb-drive/</guid><description>
&lt;p>I have a goal of booting an ESXi host from a single 32GB USB thumb drive. No
other internal storage should be required for this firewall application. This
is an ideal setup as there are no moving parts or cables to come unplugged. USB
thumb drives are cheap and fast these days.&lt;/p>
&lt;p>I was able to install ESXi 6.5 onto the USB thumb drive, but nothing shows up as
an available data store for virtual machines. There&amp;rsquo;s a ton of free space on
the USB stick. We can make use of this space with some partitioning magic.&lt;/p>
&lt;figure class="card rounded p-2 td-post-card mb-4 mt-4" style="max-width: 810px">
&lt;img class="card-img-top" src="http://openinfrastructure.co/blog/2017/11/22/esxi-6.5-single-usb-thumb-drive/datastores_hudfcda79cf55823340af4513a2f2a6dcb_92706_800x0_resize_catmullrom_3.png" width="800" height="517">
&lt;figcaption class="card-body px-0 pt-2 pb-0">
&lt;p class="card-text">
&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>I&amp;rsquo;m doing this all with a Mac OS X workstation. I&amp;rsquo;ll use a Ubuntu 16.04
VirtualBox instance to partition the USB stick. We&amp;rsquo;ll format the filesystem on
the ESXi host itself.&lt;/p>
&lt;p>First install, ESX onto the USB stick.&lt;/p>
&lt;p>Shutdown the ESX host, remove the USB stick and insert into your Mac. Eject the
disk from your mac so it can be passed through VirtualBox to Ubuntu 16.04:&lt;/p>
&lt;pre>&lt;code>sudo diskutil list
sudo diskutil eject disk1
&lt;/code>&lt;/pre>
&lt;p>To quickly get a Ubuntu desktop up and running, use vagrant:&lt;/p>
&lt;pre>&lt;code>mkdir ~/xenial
cd ~/xenial
vagrant init ubuntu/xenial64
&lt;/code>&lt;/pre>
&lt;p>Patch the vagrant file to get the GUI:&lt;/p>
&lt;pre tabindex="0">&lt;code>--- Vagrantfile.orig 2017-11-22 16:03:04.000000000 -0800
+++ Vagrantfile 2017-11-22 16:04:49.000000000 -0800
@@ -57,4 +57,8 @@
# vb.memory = &amp;#34;1024&amp;#34;
# end
+ config.vm.provider &amp;#34;virtualbox&amp;#34; do |vb|
+ vb.gui = true
+ vb.memory = &amp;#34;2048&amp;#34;
+ end
#
# View the documentation for the provider you are using for more
&lt;/code>&lt;/pre>&lt;p>Bring up the vagrant instance:&lt;/p>
&lt;pre>&lt;code>vagrant up
&lt;/code>&lt;/pre>
&lt;p>Shutdown the instance and add USB to the virtual machine:&lt;/p>
&lt;pre>&lt;code>vagrant ssh -- sudo shutdown -h now
&lt;/code>&lt;/pre>
&lt;p>Go into VirtualBox Settings =&amp;gt; Ports =&amp;gt; Add a USB EHCI controller. Add a filter
for the USB thumb drive. This is important, otherwise the USB thumb drive won&amp;rsquo;t
show up in the Ubuntu VM. If the USB thub drive doesn&amp;rsquo;t show up in the GUI,
make sure it&amp;rsquo;s been ejected from Mac OS X using &lt;code>diskutil eject&lt;/code> prior to going
into VirtualBox settings.&lt;/p>
&lt;p>Install the Ubuntu Desktop:&lt;/p>
&lt;pre>&lt;code>vagrant ssh -- sudo apt-get install -y --no-install-recommends ubuntu-desktop
&lt;/code>&lt;/pre>
&lt;p>Install gparted&lt;/p>
&lt;pre>&lt;code>vagrant ssh -- sudo apt-get install -y gparted
&lt;/code>&lt;/pre>
&lt;p>Set a password for the user &lt;code>ubuntu&lt;/code>:&lt;/p>
&lt;pre>&lt;code>vagrant ssh -- sudo passwd ubuntu
&lt;/code>&lt;/pre>
&lt;p>Reboot again to get the desktop up and running.&lt;/p>
&lt;pre>&lt;code>vagrant ssh -- sudo shutdown -r now
&lt;/code>&lt;/pre>
&lt;p>Log in as ubuntu with the password just set. Open a terminal with &lt;code>ctrl&lt;/code> +
&lt;code>alt&lt;/code> + &lt;code>t&lt;/code>.&lt;/p>
&lt;p>Use &lt;code>sudo gparted&lt;/code> to create a new partition in the free space. Make sure to create
it as unformatted, not the default of ext4.&lt;/p>
&lt;figure class="card rounded p-2 td-post-card mb-4 mt-4" style="max-width: 810px">
&lt;img class="card-img-top" src="http://openinfrastructure.co/blog/2017/11/22/esxi-6.5-single-usb-thumb-drive/partition_hu96bc1105b72be28ab53077bc8623a3c1_192723_800x0_resize_catmullrom_3.png" width="800" height="548">
&lt;figcaption class="card-body px-0 pt-2 pb-0">
&lt;p class="card-text">
&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Note the partition number, it should be partition 2, e.g. &lt;code>/dev/sdc2&lt;/code>.&lt;/p>
&lt;figure class="card rounded p-2 td-post-card mb-4 mt-4" style="max-width: 810px">
&lt;img class="card-img-top" src="http://openinfrastructure.co/blog/2017/11/22/esxi-6.5-single-usb-thumb-drive/partitionnumber_hudde5a6c7f52a2b2a62c8975df73c8180_192098_800x0_resize_catmullrom_3.png" width="800" height="512">
&lt;figcaption class="card-body px-0 pt-2 pb-0">
&lt;p class="card-text">
&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Use &lt;code>sudo gdisk /dev/sdc&lt;/code> to change the partition type to &lt;code>fb00&lt;/code>. The sequence here is:&lt;/p>
&lt;ol>
&lt;li>&lt;code>t&lt;/code>&lt;/li>
&lt;li>&lt;code>2&lt;/code>&lt;/li>
&lt;li>&lt;code>fb00&lt;/code>&lt;/li>
&lt;li>&lt;code>w&lt;/code>&lt;/li>
&lt;li>&lt;code>Y&lt;/code>&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code>vagrant ssh -- sudo gdisk /dev/sdc
GPT fdisk (gdisk) version 1.0.1
Partition table scan:
MBR: protective
BSD: not present
APM: not present
GPT: present
Found valid GPT with protective MBR; using GPT.
Command (? for help): t
Partition number (1-9): 2
Current type is &amp;#39;Linux filesystem&amp;#39;
Hex code or GUID (L to show codes, Enter = 8300): fb00
Changed type of partition to &amp;#39;VMWare VMFS&amp;#39;
Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!
Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sdc.
&lt;/code>&lt;/pre>&lt;p>Finally, change partition 2 to partition 10 to avoid issues updating ESXi 6.5.
The update process assumes partition 2 has not been created and will error out
if present.&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo sfdisk -d /dev/sdc &amp;gt; esxi.txt
cp -p esxi.txt esxi.txt.orig
&lt;/code>&lt;/pre>&lt;p>Change &lt;code>esxi.txt&lt;/code> as the following diff shows, moving partition 2 to 10.&lt;/p>
&lt;pre tabindex="0">&lt;code>--- esxi.txt.orig 2017-11-23 00:13:57.561990531 +0000
+++ esxi.txt 2017-11-23 00:15:35.566968530 +0000
@@ -7,5 +7,4 @@
/dev/sdc1 : start= 64, size= 8128, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=439DBC97-6DB2-4FD1-BDEA-A01FC9F26A49
-/dev/sdc2 : start= 8134656, size= 51927040, type=AA31E02A-400F-11DB-9590-000C2911D1B8, uuid=7661DD41-6B25-4ACA-9A7E-E68F07361B9E
/dev/sdc5 : start= 8224, size= 511968, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, uuid=CC0591B2-6658-4A25-91CF-1A9765D239A5
/dev/sdc6 : start= 520224, size= 511968, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, uuid=CDD8851F-3A51-47AD-80E1-F2D504197A8C
@@ -13,2 +12,3 @@
/dev/sdc8 : start= 1257504, size= 585696, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, uuid=3119D6C6-3EEC-4970-9289-6128686849EB
/dev/sdc9 : start= 1843200, size= 5242880, type=9D275380-40AD-11DB-BF97-000C2911D1B8, uuid=7A6D08A3-6E3F-488D-8F3B-36145382BA9F
+/dev/sdc10 : start= 8134656, size= 51927040, type=AA31E02A-400F-11DB-9590-000C2911D1B8, uuid=7661DD41-6B25-4ACA-9A7E-E68F07361B9E
&lt;/code>&lt;/pre>&lt;p>Write the partition table back out to the USB drive:&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo sfdisk --force /dev/sdc &amp;lt; esxi.txt
&lt;/code>&lt;/pre>&lt;p>Check the partition table, make sure there is a partition 10:&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo fdisk -l /dev/sdc
&lt;/code>&lt;/pre>&lt;p>Insert the USB thumb drive back in the ESXi host and boot it back up. SSH in
as root and check the partition table. There should be no partition 2 and you
should see partition 10.&lt;/p>
&lt;pre tabindex="0">&lt;code>[root@esxi:~] partedUtil getptbl /dev/disks/mpx.vmhba32\:C0\:T0\:L0
gpt
3825 255 63 61457664
1 64 8191 C12A7328F81F11D2BA4B00A0C93EC93B systemPartition 128
5 8224 520191 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
6 520224 1032191 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
7 1032224 1257471 9D27538040AD11DBBF97000C2911D1B8 vmkDiagnostic 0
8 1257504 1843199 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
9 1843200 7086079 9D27538040AD11DBBF97000C2911D1B8 vmkDiagnostic 0
10 8134656 61456383 AA31E02A400F11DB9590000C2911D1B8 vmfs 0
&lt;/code>&lt;/pre>&lt;p>Format the partition with &lt;code>vmkfstools -C vmfs5 -S USB.1&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code>[root@esxi:~] vmkfstools -C vmfs5 -S USB.1 /dev/disks/mpx.vmhba32\:C0\:T0\:L0:10
create fs deviceName:&amp;#39;/dev/disks/mpx.vmhba32:C0:T0:L0:10&amp;#39;, fsShortName:&amp;#39;vmfs5&amp;#39;, fsName:&amp;#39;USB.1&amp;#39;
deviceFullPath:/dev/disks/mpx.vmhba32:C0:T0:L0:10 deviceFile:mpx.vmhba32:C0:T0:L0:10
ATS on device /dev/disks/mpx.vmhba32:C0:T0:L0:10: not supported
.
Checking if remote hosts are using this device as a valid file system. This may take a few seconds...
Creating vmfs5 file system on &amp;#34;mpx.vmhba32:C0:T0:L0:10&amp;#34; with blockSize 1048576 and volume label &amp;#34;USB.1&amp;#34;.
Successfully created new volume: 5a1614ce-846cd3c8-9b10-0cc47aaaf624
&lt;/code>&lt;/pre>&lt;p>The partition now shows up in the datastore browser after a refresh.&lt;/p>
&lt;figure class="card rounded p-2 td-post-card mb-4 mt-4" style="max-width: 810px">
&lt;img class="card-img-top" src="http://openinfrastructure.co/blog/2017/11/22/esxi-6.5-single-usb-thumb-drive/populated_datastores_hudfcda79cf55823340af4513a2f2a6dcb_99321_800x0_resize_catmullrom_3.png" width="800" height="517">
&lt;figcaption class="card-body px-0 pt-2 pb-0">
&lt;p class="card-text">
&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Configure &lt;a href="https://labs.vmware.com/vmtj/vmware-esx-memory-resource-management-swap">swap&lt;/a>, a &lt;a href="https://kb.vmware.com/s/article/1033696">persistent scratch location&lt;/a>, and virtual machines
on the same USB drive ESX is booting from, and enjoy!&lt;/p></description></item><item><title>Blog: Puppet Enterprise Node Classification Backup &amp; Restore with ncio</title><link>http://openinfrastructure.co/blog/2016/08/21/puppet-enterprise-node-classification-backup-restore-with-ncio/</link><pubDate>Sun, 21 Aug 2016 12:48:00 +0000</pubDate><guid>http://openinfrastructure.co/blog/2016/08/21/puppet-enterprise-node-classification-backup-restore-with-ncio/</guid><description>
&lt;p>I&amp;rsquo;m happy to announce &lt;a href="https://github.com/jeffmccune/ncio">ncio&lt;/a>, a small command line utility to backup and
restore Puppet Enterprise Node Classification data.&lt;/p>
&lt;p>A customer recently needed to automate the process of backing up and restoring
&lt;a href="https://puppet.com/product">Puppet Enterprise&lt;/a>. Most of the work involved in accomplishing this
goal is fairly straight-forward. The majority of the Puppet configuration is
stored in version control in a &lt;a href="https://github.com/puppetlabs/control-repo">Control Repository&lt;/a> and Git is
incredibly easy to backup and restore. Most customers I work with don&amp;rsquo;t mind
losing data stored in PuppetDB because Puppet reports, resource information,
and facts are automatically re-populated as nodes check in after the system is
restored. The certificates used by Puppet are also fairly straight forward to
backup as they live on the local filesystem.&lt;/p>
&lt;p>The only service that was difficult to backup using normal filesystem tools is
the Node Classification Service. The node classifier stores critical
information and as a result it needs to be backed up and restored along side
all of these other resources.&lt;/p>
&lt;p>The &lt;a href="https://docs.puppet.com/pe/2016.2/nc_index.html">Node classification v1 API&lt;/a> is an excellent mechanism to retrieve
and restore node classification data, but the task of using the API is largely
left as an exercise for the reader. In order to help with this common problem,
I wrote a small utility called ncio (node classification input / ouput). If
you&amp;rsquo;d like to easily get a dump of all node classification data in
pretty-printed JSON, transform a backup for restoration on a different PE
Monolithic Master, or restore a backup then this tool is for you.&lt;/p>
&lt;p>The tool is distributed and updated on &lt;a href="https://rubygems.org/gems/ncio/">rubygems&lt;/a> in an effort to make
it easiy to install and upgrade in the future.&lt;/p>
&lt;p>Here&amp;rsquo;s how to get started:&lt;/p>
&lt;h1 id="installation">Installation&lt;/h1>
&lt;p>Installation is straight-forward thanks to Puppet Enterprise shipping with the
gem command:&lt;/p>
&lt;pre>&lt;code>$ sudo /opt/puppetlabs/puppet/bin/gem install ncio
Successfully installed ncio-1.1.0
Parsing documentation for ncio-1.1.0
Installing ri documentation for ncio-1.1.0
Done installing documentation for ncio after 0 seconds
1 gem installed
&lt;/code>&lt;/pre>
&lt;h1 id="usage">Usage&lt;/h1>
&lt;p>The command runs best from a PE Monolithic Master. The tool automatically uses
SSL certificates which are already present to make setup as easy as possible.&lt;/p>
&lt;pre>&lt;code>sudo -H -u pe-puppet /opt/puppetlabs/puppet/bin/ncio backup &amp;gt; /var/tmp/backup.json
I, [2016-06-28T19:25:55.507684 #2992] INFO -- : Backup completed successfully!
&lt;/code>&lt;/pre>
&lt;h2 id="retrying-connections">Retrying Connections&lt;/h2>
&lt;p>When automating a backup from cron, it&amp;rsquo;s recommended to use the
&lt;code>--retry-connections&lt;/code> global option to make the backup as robust as possible.
This option allows &lt;code>ncio&lt;/code> to retry in certain situations, e.g. when the
puppetserver service is restarting.&lt;/p>
&lt;h2 id="restore-pipelines">Restore Pipelines&lt;/h2>
&lt;p>The command is designed to send a backup to standard output and restore from
standard input. This allows backup and restore pipelines. In this example a
backup is taken on &lt;code>master1&lt;/code>, transformed so that it can be restored on
&lt;code>master2&lt;/code>, then restored on &lt;code>master2&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#8a8a8a;background-color:#1c1c1c;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0087ff">export&lt;/span> &lt;span style="color:#0087ff">PATH&lt;/span>=&lt;span style="color:#00afaf">&amp;#34;/opt/pupeptlabs/puppet/bin:&lt;/span>&lt;span style="color:#0087ff">$PATH&lt;/span>&lt;span style="color:#00afaf">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ncio --uri https://master1.puppet.vm:4433/classification-api/v1 backup &lt;span style="color:#af0000">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#af0000">&lt;/span> | ncio transform --hostname master1.puppet.vm:master2.puppet.vm &lt;span style="color:#af0000">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#af0000">&lt;/span> | ncio --uri https://master2.puppet.vm:4433/classification-api/v1 restore
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="command-overview">Command overview&lt;/h1>
&lt;p>Global options:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ ncio --help
usage: ncio [GLOBAL OPTIONS] SUBCOMMAND [ARGS]
Sub Commands:
backup Backup Node Classification resources
restore Restore Node Classification resources
transform Transform a backup, replacing hostnames
Quick Start: On the host of the Node Classifier service, as root or pe-puppet
(to read certs and keys)
/opt/puppetlabs/puppet/bin/ncio backup &amp;gt; groups.$(date +%s).json
/opt/puppetlabs/puppet/bin/ncio restore &amp;lt; groups.1467151827.json
Transformation:
ncio --uri https://master1.puppet.vm:4433/classification-api/v1 backup \
| ncio transform --hostname master1.puppet.vm:master2.puppet.vm \
| ncio --uri https://master2.puppet.vm:4433/classification-api/v1 restore
Global options: (Note, command line arguments supersede ENV vars in {}&amp;#39;s)
-u, --uri=&amp;lt;s&amp;gt; Node Classifier service uri {NCIO_URI}
(default: https://localhost:4433/classifier-api/v1)
-c, --cert=&amp;lt;s&amp;gt; White listed client SSL cert {NCIO_CERT}
See: https://goo.gl/zCjncC (default:
/etc/puppetlabs/puppet/ssl/certs/pe-internal-orchestrator.pem)
-k, --key=&amp;lt;s&amp;gt; Client RSA key, must match certificate {NCIO_KEY} (default:
/etc/puppetlabs/puppet/ssl/private_keys/pe-internal-orchestrator.pem)
-a, --cacert=&amp;lt;s&amp;gt; CA Cert to authenticate the service uri {NCIO_CACERT}
(default: /etc/puppetlabs/puppet/ssl/certs/ca.pem)
-l, --logto=&amp;lt;s&amp;gt; Log file to write to or keywords STDOUT,
STDERR {NCIO_LOGTO} (default: STDERR)
-s, --syslog, --no-syslog Log to syslog (default: true)
-v, --verbose Set log level to INFO
-d, --debug Set log level to DEBUG
-r, --retry-connections Retry API connections, e.g. waiting for the
service to come online. {NCIO_RETRY_CONNECTIONS}
-o, --connect-timeout=&amp;lt;i&amp;gt; Retry &amp;lt;i&amp;gt; seconds if --retry-connections=true
{NCIO_CONNECT_TIMEOUT} (default: 120)
-e, --version Print version and exit
-h, --help Show this message
&lt;/code>&lt;/pre>&lt;h2 id="backup-options">Backup options&lt;/h2>
&lt;pre tabindex="0">&lt;code>$ ncio backup --help
Node Classification backup options:
-g, --groups, --no-groups Operate against NC groups. See: https://goo.gl/QD6ZdW (default: true)
-f, --file=&amp;lt;s&amp;gt; File to operate against {NCIO_FILE} or STDOUT, STDERR (default: STDOUT)
-h, --help Show this message
&lt;/code>&lt;/pre>&lt;h2 id="transform-options">Transform options&lt;/h2>
&lt;pre tabindex="0">&lt;code>$ ncio transform --help
Node Classification transformations
Note: Currently only Monolithic (All-in-one) deployments are supported.
Transformation matches against class names assigned to groups. Transformation
of hostnames happen against rules assigned to groups and class parameters for
matching classes.
Options:
-c, --class-matcher=&amp;lt;s&amp;gt; Regexp matching classes assigned to groups.
Passed to Regexp.new() (default: ^puppet_enterprise)
-i, --input=&amp;lt;s&amp;gt; Input file path or keywords STDIN, STDOUT, STDERR (default: STDIN)
-o, --output=&amp;lt;s&amp;gt; Output file path or keywords STDIN, STDOUT, STDERR (default: STDOUT)
-h, --hostname=&amp;lt;s+&amp;gt; Replace the fully qualified domain name on the left with the
right, separated with a :
e.g --hostname master1.acme.com:master2.acme.com
-e, --help Show this message
&lt;/code>&lt;/pre>&lt;h2 id="restore-options">Restore options&lt;/h2>
&lt;pre tabindex="0">&lt;code>$ ncio restore --help
Node Classification restore options:
-g, --groups, --no-groups Operate against NC groups.
See: https://goo.gl/QD6ZdW (default: true)
-f, --file=&amp;lt;s&amp;gt; File to operate against {NCIO_FILE} or STDOUT,
STDERR (default: STDIN)
-h, --help Show this message
&lt;/code>&lt;/pre>&lt;p>Hopefully you find &lt;a href="https://github.com/jeffmccune/ncio">ncio&lt;/a> useful. If so, please let me know! If you run
into any issues or would like to see additional features, please open up an
issue on the project page.&lt;/p></description></item></channel></rss>