<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[High On Cloud]]></title><description><![CDATA[Exploring Oracle Cloud]]></description><link>https://highoncloud.co.uk/</link><image><url>https://highoncloud.co.uk/favicon.png</url><title>High On Cloud</title><link>https://highoncloud.co.uk/</link></image><generator>Ghost 5.75</generator><lastBuildDate>Sat, 18 Apr 2026 01:50:48 GMT</lastBuildDate><atom:link href="https://highoncloud.co.uk/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Fast-or-Verify Pattern]]></title><description><![CDATA[<p>Most NL&#x2192;SQL demos are impressive&#x2026; right up to the moment you need <strong>real</strong> guarantees. In finance, healthcare, or regulated ops, &#x201C;pretty good&#x201D; isn&#x2019;t good enough.</p><p>This article lays out a <strong>two&#x2011;lane architecture </strong>that answers known questions instantly and routes novel ones</p>]]></description><link>https://highoncloud.co.uk/no-new-sql/</link><guid isPermaLink="false">68efef1e5699b17392461521</guid><dc:creator><![CDATA[Shahvaiz Janjua]]></dc:creator><pubDate>Wed, 15 Oct 2025 19:11:59 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2025/10/Gemini_Generated_Image_jyihxqjyihxqjyih.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2025/10/Gemini_Generated_Image_jyihxqjyihxqjyih.png" alt="Fast-or-Verify Pattern"><p>Most NL&#x2192;SQL demos are impressive&#x2026; right up to the moment you need <strong>real</strong> guarantees. In finance, healthcare, or regulated ops, &#x201C;pretty good&#x201D; isn&#x2019;t good enough.</p><p>This article lays out a <strong>two&#x2011;lane architecture </strong>that answers known questions instantly and routes novel ones to <strong>human&#x2011;in&#x2011;the&#x2011;loop</strong> (HITL) verification and then learns from every review.</p><p>Think of it like airport security:</p><p><strong>TSA Pre&#x2713;&#xAE;</strong> for vetted passengers (fast, cheap, repeatable) and <strong>manual screening</strong> for everyone else (slower, safer, documented). Same airport, two speeds, one goal: <strong>trust</strong>!</p><figure class="kg-card kg-image-card"><img src="https://media.licdn.com/dms/image/v2/D4E12AQFQsRnBpYEbEg/article-inline_image-shrink_1500_2232/B4EZkvH1aMGYAY-/0/1757432208048?e=1767830400&amp;v=beta&amp;t=Q8fgSOZN_kOzKWvBfwRnzNBi0y1Lvz8ND0l49JVJn50" class="kg-image" alt="Fast-or-Verify Pattern" loading="lazy"></figure><hr><h3 id="lane-1the-fast-lane-reuse-don%E2%80%99t-regenerate">Lane 1 - The Fast Lane (Reuse, don&#x2019;t regenerate)</h3><p>1) <strong>Semantic lookup inside the database.</strong> When a user asks, the agent runs a similarity search over a repository of <strong>previously approved question&#x21C4;SQL pairs</strong> stored as embeddings in <strong>Oracle Autonomous Database 23ai AI Vector Search</strong>. If it finds a <em>true</em> match, it <strong>reuses</strong> the human&#x2011;approved SQL - no new generation, no chance to hallucinate new SQL.</p><p>2) <strong>LLM&#x2011;as&#x2011;a&#x2011;Judge gate.</strong> A lightweight judge agent checks whether the new question is <strong>semantically identical</strong> to the validated one (e.g., cosine threshold <strong>and</strong> schema terms align). If yes &#x2192; <strong>execute the approved SQL</strong> and return the answer. If not &#x2192; route to the Quality Lane.</p><p><strong>Why this works:</strong> you avoid generation entirely for common asks. Over time, your &#x201C;approved SQL cache&#x201D; compounds - <em>faster answers, lower cost, higher consistency</em>.</p><hr><h3 id="lane-2the-quality-lane-generate-pause-verify-learn">Lane 2 - The Quality Lane (Generate, pause, verify, learn)</h3><p>For novel/ambiguous asks we <strong>generate carefully and verify before execution</strong>:</p><p>1) <strong>Clarify &amp; improve the question</strong> (short feedback loop).</p><p>2) <strong>Generate candidate SQL</strong> using <strong>Select AI</strong> <em>in&#x2011;database</em> - keeping data movement minimal and auditability high. Don&#x2019;t execute yet.</p><p>3) <strong>HITL checkpoint with interrupts.</strong> The agent <strong>pauses</strong>; an analyst verifies business logic, schema, joins, filters, limits and then will either Approve &#x2192; execute, or Reject &#x2192; the agent revises and resubmits.</p><p>4) <strong>Close the loop.</strong> Store the now&#x2011;approved question&#x21C4;SQL as an embedding for future Fast&#x2011;Lane reuse (with lineage + reviewer + timestamp).</p><blockquote>Reality check: NL&#x2192;SQL is powerful <strong>but</strong> can mis-join, mis-filter or reference non-existent columns/tables. The human checkpoint is your safety valve.</blockquote><hr><h3 id="demo-repo-ui">Demo repo &amp; UI</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/shjanjua/fast-or-verify-nl2sql?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - shjanjua/fast-or-verify-nl2sql</div><div class="kg-bookmark-description">Contribute to shjanjua/fast-or-verify-nl2sql development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="Fast-or-Verify Pattern"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">shjanjua</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/83283d124637a7ff3e45f268fa0667c50eddd7b62d6045a204fd21bbfbe10995/shjanjua/fast-or-verify-nl2sql" alt="Fast-or-Verify Pattern"></div></a></figure><p>I&apos;ve published a <strong>Gradio</strong> demo that shows the main moving parts with a clean UI. <strong>What it includes today:</strong></p><ul><li><strong>LLM-as-a-Judge</strong> decisioning (the &#x201C;is this semantically the same?&#x201D; gate).</li><li><strong>Fast Lane</strong> behavior for returning <strong>pre-approved SQL</strong> when there&#x2019;s a true match (no execution).</li><li><strong>Quality Lane (partial):</strong> up to <strong>NL&#x2192;SQL generation</strong> (SQL is produced and displayed, not executed).</li><li><strong>Confidence label: </strong>&quot;High/Medium/Low&quot; classification for the generated SQL.</li></ul><p><strong>What it does <em>not</em> include:</strong></p><ul><li><strong>Human-in-the-loop review UI</strong> (approve/reject/edit) and the asynchronous pause/resume cycle.</li><li><strong>Automatic vectorization &amp; storage</strong> of new question&#x21C4;SQL approvals (i.e., embedding + metadata write-back).</li><li><strong>SQL execution</strong> against a database.</li></ul><p>&#x1F449; <strong>Repo:</strong> <a href="https://github.com/shjanjua/fast-or-verify-nl2sql?ref=highoncloud.co.uk" rel="noopener noreferrer">https://github.com/shjanjua/fast-or-verify-nl2sql</a> - it&#x2019;s built with <strong>Gradio</strong> so you can run and share the demo quickly without front-end heavy lifting.</p><blockquote>Pre-reqs: Oracle Database with Select AI configured and a vector table containing approved Question, SQL &amp; Embedded-Question for Fast-Lane lookup.</blockquote><hr><h3 id="under-the-hood">Under the Hood</h3><ul><li><strong>Oracle Autonomous Database 23ai &#x2013; AI Vector Search</strong>: semantic lookup of approved Q&#x21C4;SQL right where your data lives. <a href="https://docs.oracle.com/en/database/oracle/oracle-database/23/nfcoa/ai_vector_search.html?utm_source=chatgpt.com" rel="noopener noreferrer">Oracle Documentation</a></li><li><strong>Select AI</strong>: in-database NL&#x2192;SQL + RAG for the Quality Lane. <a href="https://docs.oracle.com/en-us/iaas/autonomous-database-serverless/doc/sql-generation-ai-autonomous.html?utm_source=chatgpt.com" rel="noopener noreferrer">Oracle Documentation</a></li><li><strong>OCI Generative AI</strong>: managed LLMs/embeddings for judging, rewriting and confidence scoring.</li><li><strong>Python + LangGraph</strong>: multi-step agent.</li><li><strong>Gradio</strong>: lightweight UI to expose the flow and show the SQL being produced.</li></ul><hr><h3 id="guardrails-that-matter">Guardrails that matter</h3><ul><li><strong>Least privilege:</strong> read-only roles/views; block DDL/DML by policy.</li><li><strong>Dual thresholds:</strong> similarity <strong>and</strong> judge verdict; escalate to HITL on doubt.</li><li><strong>Query &#x201C;unit tests&#x201D;</strong> (row-count bounds, KPI validations, time windows) &#x2014; <strong>not in demo</strong>.</li><li><strong>&#x201C;Show your work&#x201D; UX:</strong> SQL preview, tables touched, filter summary (the demo previews SQL only).</li><li><strong>Governance metadata</strong> (reviewer, version, glossary tags) &#x2014; <strong>not in demo</strong>.</li></ul><hr><h3 id="what-to-measure-once-you-wire-in-hitl-execution">What to measure (once you wire in HITL + execution)</h3><ul><li><strong>Fast-Lane hit rate</strong> (% answered via reuse).</li><li><strong>Time-to-answer</strong> (p50/p95) and <strong>review latency</strong>.</li><li><strong>HITL approval rate</strong> and <strong>revision count</strong>.</li><li><strong>Escapes</strong> (post-hoc corrections) &#x2192; target near-zero.</li><li><strong>Cost per answer</strong> vs BI baseline.</li></ul><hr><h3 id="conclusion">Conclusion</h3><p>This pattern doesn&#x2019;t replace experts, it <strong>scales</strong> them. Every human&#x2011;approved query becomes a reusable asset, turning your conversational layer into a <strong>trust machine</strong> over time</p>]]></content:encoded></item><item><title><![CDATA[Securing Backend APIs with GCP API Gateway and Auth0]]></title><description><![CDATA[<p>You&apos;ve had your billion dollar, unicorn start-up idea. Your mind is already racing with the architecture, frontend and backend code you&apos;ll need to make it a reality. Your main business logic will be served by the backend code. This will (usually) be accessed through RESP APIs</p>]]></description><link>https://highoncloud.co.uk/securing-backend-apis-with-gcp-api-gateway-and-auth0/</link><guid isPermaLink="false">68614e865699b1739246145e</guid><dc:creator><![CDATA[Usamah Ali Hussain]]></dc:creator><pubDate>Tue, 15 Jul 2025 11:23:24 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2025/07/Screenshot-2025-07-10-at-19.27.42.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2025/07/Screenshot-2025-07-10-at-19.27.42.png" alt="Securing Backend APIs with GCP API Gateway and Auth0"><p>You&apos;ve had your billion dollar, unicorn start-up idea. Your mind is already racing with the architecture, frontend and backend code you&apos;ll need to make it a reality. Your main business logic will be served by the backend code. This will (usually) be accessed through RESP APIs that you&apos;ve so lovingly and tenderly handcrafted with a sweet kiss on the forehead that only a loving mother could hope to replicate. </p><p>Now, time to secure your precious childre- I mean APIs, from potential harm...</p><p> </p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="background" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Background</span></h2>
                    
                    
                </div>
            </div>
        </div><p>This guide will make use of Auth0 as an identity broker for a few reasons</p><ol><li>It&apos;s easy. Offload your identity and principal management to somebody else</li><li>Auth0 provides rich libraries for handling Users, registrations, SSO, password resets and, most importantly for this post, accessTokens in a straightforward manner</li></ol><p>We&apos;ll also be using Google Cloud&apos;s API Gateway as that&apos;s what I&apos;ve experimented with and written a previous post on for handling CORS preflight checks - read about that here: </p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://highoncloud.co.uk/cors-in-gcp-api-gateway/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">CORS in GCP API Gateway</div><div class="kg-bookmark-description">If you&#x2019;re like me, building an application with Backend APIs protected behind a Google Cloud API Gateway, you&#x2019;ve probably encountered a common issue: The ability to handle CORS preflight requests from your FrontEnd! This is less of an issue with the self-administered ESP and ESPv2 offerings, but using the Managed</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://highoncloud.co.uk/content/images/size/w256h256/2022/03/logo_white.png" alt="Securing Backend APIs with GCP API Gateway and Auth0"><span class="kg-bookmark-author">High On Cloud</span><span class="kg-bookmark-publisher">Usamah Ali Hussain</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://highoncloud.co.uk/content/images/2022/03/publication-cover.png" alt="Securing Backend APIs with GCP API Gateway and Auth0"></div></a></figure><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="prerequisites" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Pre-requisites</span></h2>
                    
                    
                </div>
            </div>
        </div><p>You&apos;ll need to create an Auth0 application suitable for your Front End. Once that&apos;s created, Navigate to Applications &gt; Applications and select your newly created app. In the below screen, you&apos;ll see your <code>Domain</code>. Make a note of this!</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/07/Screenshot-2025-07-01-at-08.58.02.png" class="kg-image" alt="Securing Backend APIs with GCP API Gateway and Auth0" loading="lazy" width="1026" height="898" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/07/Screenshot-2025-07-01-at-08.58.02.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2025/07/Screenshot-2025-07-01-at-08.58.02.png 1000w, https://highoncloud.co.uk/content/images/2025/07/Screenshot-2025-07-01-at-08.58.02.png 1026w" sizes="(min-width: 720px) 720px"></figure><p>Under Applications &gt; API, you&apos;ll also need to create an API to represent your endpoints. Once done, make a note of the <code>Identifier</code> (<code>Audience</code>) that you will have provided during creation:</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/07/Screenshot-2025-07-01-at-09.02.29.png" class="kg-image" alt="Securing Backend APIs with GCP API Gateway and Auth0" loading="lazy" width="1027" height="723" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/07/Screenshot-2025-07-01-at-09.02.29.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2025/07/Screenshot-2025-07-01-at-09.02.29.png 1000w, https://highoncloud.co.uk/content/images/2025/07/Screenshot-2025-07-01-at-09.02.29.png 1027w" sizes="(min-width: 720px) 720px"></figure><p>You should now have a note of your:</p><ul><li><code>Auth0 Domain</code></li><li><code>Auth0 API Audience</code></li></ul><p>You&apos;ll also need your Front End application to handle Auth0 login and send your Auth0 User&apos;s accessToken (this is important!) with each request. There is documentation on getting this for a number of frameworks, pick your poison. </p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="implementation" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Implementation</span></h2>
                    
                    
                </div>
            </div>
        </div><p>Once you&apos;ve got the pre-requisites, the implementation is rather simple. Using the OpenAPI Spec YAML file, we can setup a security attribute and apply it to all of the endpoints we want to secure.</p><p>Here is a full example that we&apos;ll break down:</p><pre><code class="language-YAML"># openapi-spec.yaml
swagger: &quot;2.0&quot;
info:
  title: &quot;Voqu VBaaS Backend API&quot;
  description: &quot;API for the Voicebot-as-a-Service platform&quot;
  version: &quot;1.0.0&quot;
schemes:
  - &quot;https&quot;
produces:
  - &quot;application/json&quot;

host: &quot;${api_gateway_managed_service}&quot;
x-google-endpoints:
  - name: &quot;${api_gateway_managed_service}&quot;
    allowCors: True

securityDefinitions:
  auth0_jwt:
    authorizationUrl: &quot;&quot;
    flow: &quot;implicit&quot;
    type: &quot;oauth2&quot;
    x-google-issuer: &quot;https://${auth0_domain}/&quot;
    x-google-jwks_uri: &quot;https://${auth0_domain}/.well-known/jwks.json&quot;
    x-google-audiences: &quot;${auth0_api_audience}&quot;

# We are returning to the path-based approach, now with the validator exception.
paths:
  /hello:
    get:
      description: &quot;A sample endpoint to test authentication.&quot;
      operationId: &quot;hello&quot;
      x-google-backend:
        address: &quot;my-super-cool-url.highoncloud.co.uk/hello&quot;
      security:
        - auth0_jwt: []
      responses:
        &quot;200&quot;:
          description: &quot;A successful response&quot;</code></pre><p>Remember, this is YAML so be careful with spacing. The above file uses 2-space indents. </p><p>The 2 aspects we&apos;re using to secure our endpoints in the above file are:</p><ul><li><code>securityDefinitions</code> - This block defines how the security will be checked, the JWT token issuer, URI and defined audiences</li><li><code>security</code> - This block is defined as a parameter in each endpoint method that we wish to secure allowing modular application of security where required</li></ul><p>The entire flow works as follows:</p><ol><li>The request reaches GCP API gateway with a header of <code>Authorization: Bearer {token}</code> as is the standard for Access Tokens</li><li>GCP API Gateway sees that the endpoint being accessed (<code>GET on /hello</code>) has a security parameter so it expects a Token to be checked before allowing the request to pass through</li><li>The GCP API gateway takes the provided token and uses the auth0 domain, JWKS URI and audiences to validate that the token belongs to a valid Auth0 user and contains the correct claims. </li><li>If all checks succeed, the request is forwarded to the backend.</li><li>If the checks fail i.e. the JWT token has expired or does not contain all of the correct, required claims, an error 401 Unauthorized is returned.</li></ol><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="deployment" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Deployment</span></h2>
                    
                    
                </div>
            </div>
        </div><p>For deployment, this guide uses terraform.</p><p> The below snippets creates everything you need. Ensure your API Spec YAML file is in your main terraform directory alongside the below code.</p><ul><li>Enable the API Gateway Service</li></ul><pre><code class="language-HCL">resource &quot;google_project_service&quot; &quot;apigateway&quot; {
  project            = var.project_id
  service            = &quot;apigateway.googleapis.com&quot;
  disable_on_destroy = false
}</code></pre><ul><li>Create the Gateway API Resource as a container for the YAML configuration</li></ul><pre><code class="language-HCL">resource &quot;google_api_gateway_api&quot; &quot;vbaas_api&quot; {
  provider = google-beta # API Gateway resources are often updated in the beta provider
  project  = var.project_id
  api_id   = &quot;vbaas-backend-api&quot;
  display_name = &quot;VBaaS Backend API&quot;
  depends_on = [google_project_service.apigateway]
}</code></pre><ul><li>Store the OpenAPI Spec YAML file and pass in the required variables</li></ul><pre><code class="language-HCL">resource &quot;google_api_gateway_api_config&quot; &quot;vbaas_api_config&quot; {
  provider     = google-beta
  project      = var.project_id
  api          = google_api_gateway_api.vbaas_api.api_id
  api_config_id_prefix = &quot;vbaas-config-&quot;
  display_name = &quot;Voqu VBaaS Config v1.0.0&quot;

  openapi_documents {
    document {
      path     = &quot;openapi-spec.yaml&quot;
      contents = base64encode(templatefile(&quot;${path.module}/openapi-spec.yaml&quot;, {
        auth0_domain          = var.auth0_domain
        auth0_api_audience    = var.auth0_api_audience
        api_gateway_managed_service  = var.api_gateway_managed_service
      }))
    }
  }

  # This lifecycle rule prevents downtime during updates by creating the new
  # config before destroying the old one.
  lifecycle {
    create_before_destroy = true
  }
}</code></pre><ul><li>Spin up the API Gateway</li></ul><pre><code class="language-HCL">resource &quot;google_api_gateway_gateway&quot; &quot;vbaas_gateway&quot; {
  provider     = google-beta
  project      = var.project_id
  region       = var.region
  gateway_id   = &quot;vbaas-gateway&quot;
  api_config   = google_api_gateway_api_config.vbaas_api_config.id
  display_name = &quot;Voqu VBaaS Gateway&quot;

  depends_on = [google_api_gateway_api_config.vbaas_api_config]
}</code></pre><ul><li>&lt;Optional&gt; Add an output to easily find your API Gateway URL</li></ul><pre><code class="language-HCL">output &quot;api_gateway_url&quot; {
  description = &quot;The default hostname of the deployed API Gateway.&quot;
  value       = &quot;https://${google_api_gateway_gateway.vbaas_gateway.default_hostname}&quot;
}</code></pre><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="conclusion" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Conclusion</span></h2>
                    
                    
                </div>
            </div>
        </div><p> Now, you can use Auth0 credentials to manage access to your backend APIs. The flexibility of this approach means that the security requirement can be added to any API endpoints and even specific HTTP Request methods for that endpoint, as and when required. </p>]]></content:encoded></item><item><title><![CDATA[CORS in GCP API Gateway]]></title><description><![CDATA[<p>If you&apos;re like me, building an application with Backend APIs protected behind a Google Cloud API Gateway, you&apos;ve probably encountered a common issue: The ability to handle CORS preflight requests from your FrontEnd!</p><p>This is less of an issue with the self-administered ESP and ESPv2 offerings,</p>]]></description><link>https://highoncloud.co.uk/cors-in-gcp-api-gateway/</link><guid isPermaLink="false">68613d355699b173924613d3</guid><dc:creator><![CDATA[Usamah Ali Hussain]]></dc:creator><pubDate>Mon, 07 Jul 2025 08:16:54 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2025/07/Gemini_Generated_Image_n0nf9vn0nf9vn0nf-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2025/07/Gemini_Generated_Image_n0nf9vn0nf9vn0nf-1.png" alt="CORS in GCP API Gateway"><p>If you&apos;re like me, building an application with Backend APIs protected behind a Google Cloud API Gateway, you&apos;ve probably encountered a common issue: The ability to handle CORS preflight requests from your FrontEnd!</p><p>This is less of an issue with the self-administered ESP and ESPv2 offerings, but using the Managed API Gateway offering, you relinquish control of startup options which require some careful considerations for scenarios such as handling CORS preflight requests.</p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="background" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Background</span></h2>
                    
                    
                </div>
            </div>
        </div><p>I&apos;m lazy, so here&apos;s Gemini Pro 2.5&apos;s explanation of CORS:</p><blockquote>CORS (Cross-Origin Resource Sharing) preflight <code>OPTIONS</code> requests are a security mechanism used by web browsers to check with the server if the actual request (e.g., <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, or a <code>GET</code> with custom headers) is safe to send.</blockquote><blockquote>Their primary purpose is to ensure that a server explicitly permits cross-origin requests that could have side effects on the server&apos;s data. This prevents malicious websites from making requests to an API on another domain without the API&apos;s explicit permission, enhancing web security.</blockquote><p>Now that we&apos;ve got that out of the way, let&apos;s get into issues with GCP and how to handle this.</p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="openapi-spec" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">OpenAPI Spec</span></h2>
                    
                    
                </div>
            </div>
        </div><p><em>I know AI is all the buzz right now, but this is not to be confused with OpenAI!</em></p><p>Configuration of a GCP API gateway required an OpenAPI Spec YAML file: <a href="https://cloud.google.com/api-gateway/docs/openapi-overview?ref=highoncloud.co.uk">https://cloud.google.com/api-gateway/docs/openapi-overview</a></p><p>An example of a simple OpenAPI Spec file for handling backend routes is:    </p><pre><code class="language-YAML">    swagger: &quot;2.0&quot;
    info:
      title: API_ID optional-string
      description: &quot;Get the name of an airport from its three-letter IATA code.&quot;
      version: &quot;1.0.0&quot;
    host: DNS_NAME_OF_DEPLOYED_API
    schemes:
      - &quot;https&quot;
    paths:
      &quot;/airportName&quot;:
        get:
          description: &quot;Get the airport name for a given IATA code.&quot;
          operationId: &quot;airportName&quot;
          parameters:
            -
              name: iataCode
              in: query
              required: true
              type: string
          responses:
            200:
              description: &quot;Success.&quot;
              schema:
                type: string
            400:
              description: &quot;The IATA code is invalid or missing.&quot;</code></pre><p>Here you can see we define a single <code>/airportName</code> path which accepts <code>GET</code> requests, requiring <code>parameters</code> and responding with either <code>200</code> for success or <code>400</code> for an invalid or missing IATA code.</p><p></p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="problem" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Problem</span></h2>
                    
                    
                </div>
            </div>
        </div><p>As is, the above spec will work perfectly fine for direct requests to the Gateway, assuming the backend is properly configured of course. Direct requests through Curl or a tool such as Postman will respond appropriately. The issue arises when attempting to access these endpoints using a Front End application in a Browser such as Google Chrome. As we figured out above, the browser will send a <code>HTTP OPTIONS</code> request to see if the actual request is safe to send and if the origin is accepted. </p><p>We need to inform the GCP API Gateway that it should handle these CORS Preflight Options requests otherwise it will result in all requests to this API Gateway from the frontend failing with a CORS error <code>HTTP 503 &quot;No Healthy Upstream&quot;</code>. </p><p>We have two options:</p><ol><li>Define an explicit <code>OPTIONS</code> method on every endpoint path that will be accepting requests, which will forward these to the backend on every path.</li><li>Tell the API Gateway to automatically forward all <code>OPTIONS</code> requests to the backend and handle them there. </li></ol><p>The second option makes the most sense and is specifically designed for this use-case, otherwise any change to endpoint paths, adding or removing, will need a duplicated block of code to handle those <code>OPTIONS</code> requests. Not very DRY-Programming. </p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="implementation" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Implementation</span></h2>
                    
                    
                </div>
            </div>
        </div><p>For this we&apos;ll need two aspects. </p><ol><li>Firstly we&apos;ll need to tell the API Gateway to handle all <code>OPTIONS</code> requests for defined endpoints.</li><li> Secondly, we&apos;ll need to ensure our Backend (in this example, a FastAPI Application) can handle all of these.</li></ol><p>Let&apos;s tackle the first part. For this, you&apos;ll need a few variables:</p><ul><li>API Gateway managed service name - You can get this from the GCP Console. It will look something like <code>{api_name}-ID.apigateway.{project-name}.cloud.goog</code></li></ul><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/06/Screenshot-2025-06-29-150942.png" class="kg-image" alt="CORS in GCP API Gateway" loading="lazy" width="780" height="263" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/06/Screenshot-2025-06-29-150942.png 600w, https://highoncloud.co.uk/content/images/2025/06/Screenshot-2025-06-29-150942.png 780w" sizes="(min-width: 720px) 720px"></figure><ul><li>Your backend URL - Where the actual defined endpoints are hosted. </li></ul><p></p><p>The below spec is using the example of :</p><ul><li><code>demo-gateway-18bxrikyi5dy4.apigateway.project.cloud.goog</code> as the Managed Service name and </li><li><code>my-cool-host.highoncloud.co.uk</code> as the backend URL</li></ul><pre><code class="language-YAML"># openapi-spec.yaml
swagger: &quot;2.0&quot;
info:
  title: &quot;Voqu VBaaS Backend API&quot;
  description: &quot;API for the Voicebot-as-a-Service platform&quot;
  version: &quot;1.0.0&quot;
schemes:
  - &quot;https&quot;
produces:
  - &quot;application/json&quot;

host: &quot;demo-gateway-18bxrikyi5dy4.apigateway.project.cloud.goog&quot;
x-google-endpoints:
  - name: &quot;demo-gateway-18bxrikyi5dy4.apigateway.project.cloud.goog&quot;
    allowCors: True


# We are returning to the path-based approach, now with the validator exception.
paths:
  /hello:
    get:
      description: &quot;A sample endpoint to test authentication.&quot;
      operationId: &quot;hello&quot;
      x-google-backend:
        address: &quot;my-cool-host.highoncloud.co.uk/hello&quot;
      responses:
        &quot;200&quot;:
          description: &quot;A successful response&quot;</code></pre><p>The key bit to point out here is:</p><ul><li><code>allowCors</code> - This is set on the entire API Gateway host so all <code>OPTIONS</code> requests will be forwarded</li></ul><p>That&apos;s the magical touch. It needs to be defined on the <code>x-google-endpoints</code> where the <code>name</code> parameter is set as your API Gateway Managed Service name. </p><p>Now with all of this in place, all requests sent to this API Gateway, from an application running in Chrome, which are preceded with a CORS preflight OPTIONS request, will be forwarded to the <code>x-google-backend</code>. </p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="optional-backend-config" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Optional Backend Config</span></h2>
                    
                    
                </div>
            </div>
        </div><p>We also need to ensure the backend can handle these <code>OPTIONS</code> requests. For this scenario, I&apos;m running  a FastAPI application in development mode which simplifies things. Simply adding the below block of code to your application <code>main.py</code> or otherwise will allow it to handle these appropriately.</p><ul><li>Note: The below code is only suitable for development where all origins are accepted. This is UNSECURE and should be changed to only accept the appropriate origins in production.</li></ul><pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=[&quot;*&quot;],  # Allow all origins for testing
    allow_credentials=True,
    allow_methods=[&quot;*&quot;],
    allow_headers=[&quot;*&quot;],
)</code></pre><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="conclusion" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Conclusion</span></h2>
                    
                    
                </div>
            </div>
        </div><p>And there we have it. Handling CORS Preflight requests through a GCP API Gateway is complete. </p><p>I&apos;ve been playing with building an application recently so look out for a blog post on securing your endpoints, through the GPC API Gateway, using JWT tokens and Auth0 as an identity provider.</p>]]></content:encoded></item><item><title><![CDATA[Oracle Cloud DevOps: CI/CD is real!]]></title><description><![CDATA[A guide for implementing CI/CD Automation on Oracle Cloud DevOps]]></description><link>https://highoncloud.co.uk/oci-cicd-automation/</link><guid isPermaLink="false">67c5a0b85699b17392460f86</guid><dc:creator><![CDATA[Usamah Ali Hussain]]></dc:creator><pubDate>Wed, 09 Apr 2025 13:00:19 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2025/03/DALL-E-2025-03-03-12.44.10---A-high-tech-digital-illustration-representing-code-deployment-to-serverless-computing-using-DevOps.-The-image-features-lines-of-code-flowing-through-a.webp" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2025/03/DALL-E-2025-03-03-12.44.10---A-high-tech-digital-illustration-representing-code-deployment-to-serverless-computing-using-DevOps.-The-image-features-lines-of-code-flowing-through-a.webp" alt="Oracle Cloud DevOps: CI/CD is real!"><p>This blog serves as an example of implementing a <strong>Full end-to-end</strong> <strong>automation</strong> for deploying code to a &quot;serverless&quot; function, all hosted within OCI. </p><p>It&apos;s going to revolutionise the world and how we see technology henceforth... I think.</p><h2 id="background">Background</h2><p>You have a single or multiple routine jobs that you wish to automate through a script. You want this to be deployed on the cloud, with minimal input required on management of the platform as well as reducing costs. You also want to be able to rapidly develop, test and deploy this in a collaborative fashion. </p><h2 id="solution">Solution</h2><p>What we need is a version controlled, collaborative and managed service that will scale, be maintained, only charge for actual usage and be easy enough to deploy and test.</p><p>OCI has the stack to solve all of these issues, you just don&apos;t know it yet.</p><p>Let&apos;s start with the design and work into the details of how we can fit them all together. </p><h1 id="workflow">Workflow</h1><p>I&apos;m going to show what it would actually look like for Developers to use this system first, so you get a feel for it..</p><p>You, master of coding, write some nice code and commit it to the GIT repository (with a proper Commit message of course... I&apos;m looking at my past self judgingly here)</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-17.16.23.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="496" height="201"></figure><p>Once you hit that fateful <code>Commit &amp; Push</code> button, the code, as you&apos;d expect, is pushed to the repository. Aaaaaand, that&apos;s your work done. </p><p>What happens next... is magic (or just the CD part of CI/CD)</p><p>OCI DevOps will trigger a Build Pipeline, which triggers a Deploy Pipeline, which deploys to OCI Functions. </p><p>Let&apos;s dig into the details of how this works and, more importantly, how you can implement this today</p><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="architecture" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Architecture</span></h2>
                    
                    
                </div>
            </div>
        </div><h3 id></h3><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/image-3.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="782" height="944" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/03/image-3.png 600w, https://highoncloud.co.uk/content/images/2025/03/image-3.png 782w" sizes="(min-width: 720px) 720px"></figure><p>The involved components are:</p><ul><li>OCI DevOps Project - An isolated logical environment hosting our CI/CD Resources</li><li>Code Repository - GIT repository hosted and managed by OCI</li><li>Build Pipeline - Used to build (it&apos;s in the name) and test software artifacts</li><li>Deployment Pipeline - Used to (you guessed it) deploy artifacts to target environment(s)</li><li>Container Repository - Since we&apos;re deploying Docker Containers in this use case, our artifact repository is the OCI Container Repository</li><li>OCI Functions - The target environment to which the Deploy Pipeline will, you know, deploy artifacts. </li><li>OCI Logging/Notification - Useful for (surprise surprise) logging and alerting of any happenings within the Project</li></ul><h1 id="breakdown">Breakdown</h1><p>Now let&apos;s go into each of the above components and discuss how they fit into our specific use-case</p><h2 id="oci-loggingnotifications">OCI Logging/Notifications</h2><p>When creating a DevOps Project, OCI needs a place to send all of the related alerts and logs (think Pipeline runs and failures, triggers, merges etc). For this, we&apos;ll create an <code>OCI ONS Notification Topic</code> and associate this at Project creation, allowing all the relevant notifications to be sent here.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-21-at-11.11.46.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="800" height="484" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/03/Screenshot-2025-03-21-at-11.11.46.png 600w, https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-21-at-11.11.46.png 800w" sizes="(min-width: 720px) 720px"></figure><h2 id="devops-project">DevOps Project</h2><p>This one is pretty simple. An OCI DevOps Project is a logical grouping of all the resources required to implement a CI/CD workflow. All the components we go through henceforth will sit under a single Project umbrella. </p><p>The nice thing about having everything hosted within OCI is that you no longer have to manage Identities and Access Control across multiple platforms (or try and integrate different platforms with a central LDAP provider). Everything is in one place with OCI&apos;s (quite decent if I do say so myself) Policies for granular access control. </p><h2 id="code-repository">Code Repository</h2><p>A requirement for the CI part of any CI/CD workflow is a version-control system (usually GIT unless you just love being different). </p><p>OCI hosts GIT repositories as PaaS services. This is cool because &lt;insert-usual-PaaS-benefits-here&gt;. As a user, you simply create a repository, and off you go.</p><p>Code Repositories retains all of the usual GIT features: commits, pulls, pushes, merge requests etc and make up the first step in our journey to deployment.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-17.35.41.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="843" height="370" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/03/Screenshot-2025-03-03-at-17.35.41.png 600w, https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-17.35.41.png 843w" sizes="(min-width: 720px) 720px"></figure><h2 id="pipelines">Pipelines</h2><p>A ubiquitous term in DevOps. OCI comes with two types of Pipelines allowing you to separate the stages of deployment. </p><ul><li>Build Pipeline: Used to compile, test and (key is in the name) build artifact(s). Think of creating a Spring Boot jar file with <code>java -jar</code> or using a Dockerfile to create an image.</li><li>Deploy Pipeline: Deploy your artifact to the target environment(s).</li></ul><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-17.40.54.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="715" height="594" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/03/Screenshot-2025-03-03-at-17.40.54.png 600w, https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-17.40.54.png 715w"></figure><p>Build Pipelines have a &quot;Managed Build&quot; stage which takes in a <code>build_spec.yaml</code> file with instructions on how to build the required artifact(s). You can (should) also use this stage to perform tests, however testing isn&apos;t covered as part of this guide (see disclaimer).  </p><p>Deploy Pipelines require two things:</p><ul><li>Artifact(s): These should already be compiled and stored in an Artifact Repository. In our case, this will be the container image created from our code in the Build Pipeline.</li><li>Environment: The target platform for your software. We&apos;ll be deploying directly to our OCI Function.</li></ul><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-14.35.34-copy-2.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="680" height="525" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/03/Screenshot-2025-03-03-at-14.35.34-copy-2.png 600w, https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-14.35.34-copy-2.png 680w"></figure><h2 id="trigger">Trigger</h2><p>OCI Triggers are used in the same way as any GIT trigger. We&apos;ll be waiting for pushes to the main branch (see disclaimer before you send death threats) in our Code Repository and using that to trigger the build Pipeline run. </p><p>We&apos;ll be using another trigger (but defining this one in the Build Pipeline) to kick off the Deployment Pipeline once the Build Pipeline has completed it&apos;s steps. </p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-14.35.34-1.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="819" height="449" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/03/Screenshot-2025-03-03-at-14.35.34-1.png 600w, https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-03-at-14.35.34-1.png 819w" sizes="(min-width: 720px) 720px"></figure><h2 id="buildspecyaml">build_spec.yaml</h2><p>This is the yaml file stored in the Code Repository which tells the OCI Build Pipeline how to work. You can define any number of steps and stages within this (compile, test, rollback on failures etc.).</p><p>By default, the Build Pipeline will attempt to find this in the root of your supplied Code Repository, if it is not named as expected or can be found somewhere else, you&apos;ll need to configure the Build Pipeline with this location.</p><p>In our case, we&apos;ll be using this file, along with a <code>Dockerfile</code> to build our python code into a container image,</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/Screenshot-2025-03-04-at-12.54.12-copy.png" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="443" height="541"></figure><div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="implementation" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Implementation</span></h2>
                    
                    
                </div>
            </div>
        </div><p>Now that we understand all of the individual pieces of the puzzle, let&apos;s get them all working together. </p><p>A word of warning, the next steps are quite in-depth with some moving parts.</p><h2 id="pre-requisites">Pre-requisites</h2><p>You&apos;ll need an existing OCI Function to deploy to. If you don&apos;t have a function, create one using the QuickStart Guides:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.oracle.com/en-us/iaas/Content/Functions/Tasks/functionsquickstartguidestop.htm?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Functions QuickStart Guides</div><div class="kg-bookmark-description">Get set up and running quickly using the OCI Functions QuickStart Guides.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://docs.oracle.com/favicon.ico" alt="Oracle Cloud DevOps: CI/CD is real!"></div></div></a></figure><p>You&apos;ll also need an OCI IAM User with the appropriate policies allowing it to Manage (push) OCI Container Repository images. Create an Auth token for this User and store it as a Secret in an OCI Vault in the same compartment as your DevOps project. You&apos;ll throw this Secret OCID into your <code>build_spec.yaml</code> file.</p><h1 id="step-by-step">Step-by-step</h1><p>Up to this point, what we have is all of the Building blocks for a streamlined, automated deployment. Let&apos;s get to deploying</p><h2 id="terraform">Terraform</h2><p>Before deploying, ensure you have appropriately populated the provided <code>variables.tf</code> file. The required variables should be self-explanatory (if not, write a pipeline that, when triggered - like you - will send me a strongly worded email)</p><p>The code example assumes an OCI Function already exists and we&apos;re referring to its OCID which is stored in a terraform variable.</p><p>The code will also create all of the required Policies and the dynamic group to give OCI DevOps the required permissions in the compartment.</p><p>Run the provided terraform code to spin up all the resources</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/usamahuzi/OCI-CICD/tree/prod/terraform?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">OCI-CICD/terraform at prod &#xB7; usamahuzi/OCI-CICD</div><div class="kg-bookmark-description">All the resources you need to Automatically deploy your code to OCI Functions using OCI DevOps - usamahuzi/OCI-CICD</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="Oracle Cloud DevOps: CI/CD is real!"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">usamahuzi</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/e68f6867016eddf2bc15b0eab102efcf0809b7126d70a43eab52a09cedfe9d06/usamahuzi/OCI-CICD" alt="Oracle Cloud DevOps: CI/CD is real!"></div></a></figure><ul><li>Dynamic group and associated Policies for OCI DevOps:<ul><li>Allow DevOps resources to manage all resources within the compartment</li></ul></li><li>DevOps Project with ONS Notification Topic</li><li>Code Repository and Trigger</li><li>Target Deploy Environment and Deployment Artifact</li><li>Build and Deploy Pipelines with Stages</li></ul><p></p><h2 id="code-repository-contents">Code Repository contents</h2><p>Once we have the platform, we&apos;ll need to setup the contents of the Code Repository that will allow our OCI Function to be deployed and tested.</p><h3 id="fn-project-cli">Fn project CLI</h3><p>For this, you&apos;ll need the <code>Fn project CLI</code> installed:</p><ul><li>For Windows:</li></ul><pre><code>curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh</code></pre><ul><li>For Mac:</li></ul><pre><code>brew update &amp;&amp; brew install fn</code></pre><p>Confirm installation using:</p><pre><code>fn version</code></pre><p>which should output something similar to the below (I&apos;m not using the latest version, terrible)</p><pre><code>Client version: 0.6.36 is not latest: 0.6.39</code></pre><h3 id="generic-function-code">Generic Function code</h3><p>Next you&apos;ll need to clone your Code Repository locally following this guide:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.oracle.com/en-us/iaas/Content/devops/using/clone_repo.htm?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">DevOps Git clone</div><div class="kg-bookmark-description">You can clone the code repository to create a local copy on your computer, add or remove files, commit changes, and work on different branches by using Git operations.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://docs.oracle.com/favicon.ico" alt="Oracle Cloud DevOps: CI/CD is real!"></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.oracle.com/en-us/iaas/Content/libraries/global-images/actions-menu.png" alt="Oracle Cloud DevOps: CI/CD is real!"></div></a></figure><p>Once cloned, move into your repository directory</p><pre><code>cd repository-name</code></pre><p>Initialise the function using:</p><pre><code>fn init --runtime [go|java|node|python] my-func</code></pre><p>Since this guide is creating a python function, I did:</p><pre><code>fn init --runtime python user-report-fn</code></pre><p>This will create a new directory with your function name. I&apos;ll be referring to my function name as <code>user-report-fn</code> from now on. Make sure to use the name you&apos;ve given your function.</p><p> To keep things simple, we&apos;ll move everything to the root of the Code Repository and delete the new folder:</p><pre><code>mv  user-report-fn/* ./
rmdir user-report-fn</code></pre><p>Your directory should contain the following files for a python function:</p><pre><code>func.py
func.yaml
requirements.txt</code></pre><p><code>func.py</code> - Your function code to be executed (put everything under the <code>handler</code> function)</p><p><code>func.yaml</code> - Metadata about your function such as name, version</p><p><code>requirements.txt</code> - A list of dependencies required for your python function to operate</p><p>You can find sample functions on the OCI GitHub which will give you an idea on how to get started. This guide won&apos;t cover Function development.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/oracle-samples/oracle-functions-samples?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - oracle-samples/oracle-functions-samples: Examples demonstrating how to use Oracle Functions</div><div class="kg-bookmark-description">Examples demonstrating how to use Oracle Functions - oracle-samples/oracle-functions-samples</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="Oracle Cloud DevOps: CI/CD is real!"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">oracle-samples</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/3720a4cbca6cae9f508dc7b60ccc55128af0a61a6fc9261ee41f5d11fbfe9e0d/oracle-samples/oracle-functions-samples" alt="Oracle Cloud DevOps: CI/CD is real!"></div></a></figure><h3 id="specific-code-for-this-automation">Specific Code for this automation</h3><p>Once you have your function ready for deployment, you&apos;ll need to add 2 more files to the root of the Code Repository.</p><ol><li><code>build_spec.yaml</code> - As mentioned previously, this gives the instructions to the Build Pipeline</li><li><code>Dockerfile</code> - This is required to create the Container Image that will ultimately be deployed as the OCI Function</li></ol><p>Example contents of these files can be found here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/usamahuzi/OCI-CICD/tree/prod/code-repository-contents?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">OCI-CICD/code-repository-contents at prod &#xB7; usamahuzi/OCI-CICD</div><div class="kg-bookmark-description">All the resources you need to Automatically deploy your code to OCI Functions using OCI DevOps - usamahuzi/OCI-CICD</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="Oracle Cloud DevOps: CI/CD is real!"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">usamahuzi</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/acae9a3b94edee7efaa3ae44c131801838e0f8fb2971e1ec4d3ea6a16ec88b51/usamahuzi/OCI-CICD" alt="Oracle Cloud DevOps: CI/CD is real!"></div></a></figure><p>Do you care about what&apos;s going on in the <code>build_spec.yaml</code> file? If so, you need a hobby, but take a gander at the section below</p><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">build_spec.yaml</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p><span style="white-space: pre-wrap;">This file is quite important to making this all work so let&apos;s go through it. Any reference looking like </span><code spellcheck="false" style="white-space: pre-wrap;"><span>${this}</span></code><span style="white-space: pre-wrap;"> are for Build Pipeline Parameters which we have defined in our terraform and should already exist as part of the terraform deployment.</span></p><p><span style="white-space: pre-wrap;">There is a single step which will run on the OCI Build Runner (a bash shell environment) and execute our command which is running </span><code spellcheck="false" style="white-space: pre-wrap;"><span>docker build</span></code><span style="white-space: pre-wrap;"> to create the Container Image locally. </span></p><p><span style="white-space: pre-wrap;">This will finish by passing the location of the Image, as an outputArtifact, to the next stage which is the Deploy Pipeline </span></p></div>
        </div><p>Your Dockerfile can be identical to the one provided if you&apos;re using Python so easy peasy there. </p><p>Phew. I think we&apos;re ready to get this show on the road!</p><p>Push to the Code Repository and sit back to watch the magic happen.</p><h2 id="validation">Validation</h2><p>Log into the OCI Console and you should be able to see your DevOps Project containing a new Code Repository, Build Pipeline, Deploy Pipeline, Trigger, Environment and Artifact. </p><p>Any new pushes of code to the Code Repository will kick off your Pipeline process, resulting in a shiny new Function being deployed, ready to wreak havoc on your enemies... or similar.</p><h1 id="conclusion">Conclusion</h1><p>Wow. You&apos;ve reached the end. Please get some sunlight. </p><p>Here&apos;s a nice meme summing up my experience getting the tech for this blog working.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2025/03/f4f8e4fb-90d0-401e-9564-dce83557acf0.jpeg" class="kg-image" alt="Oracle Cloud DevOps: CI/CD is real!" loading="lazy" width="1579" height="886" srcset="https://highoncloud.co.uk/content/images/size/w600/2025/03/f4f8e4fb-90d0-401e-9564-dce83557acf0.jpeg 600w, https://highoncloud.co.uk/content/images/size/w1000/2025/03/f4f8e4fb-90d0-401e-9564-dce83557acf0.jpeg 1000w, https://highoncloud.co.uk/content/images/2025/03/f4f8e4fb-90d0-401e-9564-dce83557acf0.jpeg 1579w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[RAG to Reality: What is Retrieval-Augmented Generation ?]]></title><description><![CDATA[<p></p><p>This blog series is a longer format and expansion of my UK Oracle User Group presentation, titled; RAG to Reality: A deep-dive into enhancing AI with Retrieval-Augmented Generation, as well as the Oracle published blog: <a href="https://blogs.oracle.com/ai-and-datascience/post/ai-rag-solution?ref=highoncloud.co.uk" rel="noreferrer">RAG to reality: Amplify AI and cut costs</a>. A mouthful! But an accurate description.</p><p>A</p>]]></description><link>https://highoncloud.co.uk/what-is-rag/</link><guid isPermaLink="false">6756c1a0012d992ffad33411</guid><dc:creator><![CDATA[Shahvaiz Janjua]]></dc:creator><pubDate>Fri, 13 Dec 2024 20:46:08 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2024/12/Screenshot-2024-12-12-at-23.46.11.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2024/12/Screenshot-2024-12-12-at-23.46.11.png" alt="RAG to Reality: What is Retrieval-Augmented Generation ?"><p></p><p>This blog series is a longer format and expansion of my UK Oracle User Group presentation, titled; RAG to Reality: A deep-dive into enhancing AI with Retrieval-Augmented Generation, as well as the Oracle published blog: <a href="https://blogs.oracle.com/ai-and-datascience/post/ai-rag-solution?ref=highoncloud.co.uk" rel="noreferrer">RAG to reality: Amplify AI and cut costs</a>. A mouthful! But an accurate description.</p><p>A brief overview of what we&apos;ll cover in this series!</p><ul><li><strong>What is Retrieval-Augmented Generation (Part1) - [</strong>This Blog]<ul><li>Use-cases</li><li>Retrieval-Augmented Generation (RAG) Definition</li><li>Basic RAG Architecture</li><li>RAG Enhancements</li></ul></li><li>Oracle Cloud Infrastructure (OCI) Artificial Intelligence (AI) Services (Part2)</li><li>OCI AI RAG Architectures (Part3)</li><li>Easy Demo Build Guide (Part4)</li><li>Advanced RAG (Part5)</li></ul><h2 id="use-cases">Use-Cases</h2><p>Before we even get into the details of Retrieval-Augmented Generation (RAG), lets see the type of scenario that brings about the need for RAG.</p><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----12-December-2024_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----12-December-2024.mp4" poster="https://img.spacergif.org/v1/1708x896/0a/spacer.png" width="1708" height="896" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----12-December-2024_thumb.jpg&apos;) 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:14</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1&#xD7;</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p>In the above scenario, HR spend an egregious amount of time dealing with employee queries since; policies are stored across different locations (contracts stored in HCM, benefits stored in a 3rd party site etc) and different parts of the business have entirely different policies (due to mergers &amp; acquisitions). So there is not a one size fits all answer to most given queries!</p><p>Other scenarios may include;</p><ul><li><strong>Customer Support</strong>: A need to provide real-time, accurate answers based upon policy documents</li><li><strong>Healthcare</strong>: Summarise patient records and retrieve relevant clinical guidelines</li><li><strong>Legal Tech</strong>: Extracting relevant case laws from large repositories for litigation support</li><li><strong>Financial Analysis</strong>: Summarising market trends and retrieving regulatory data</li><li><strong>Academic Research</strong>: Providing Summaries of related studies and findings across journals</li></ul><h2 id="solving-the-problem">Solving the problem</h2><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="1419" height="691" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/12/image.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/12/image.png 1000w, https://highoncloud.co.uk/content/images/2024/12/image.png 1419w" sizes="(min-width: 720px) 720px"></figure><p>The features of GenAI &amp; LLMs seem to be the right fit!</p><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024-1_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024-1.mp4" poster="https://img.spacergif.org/v1/1716x758/0a/spacer.png" width="1716" height="758" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024-1_thumb.jpg&apos;) 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:22</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1&#xD7;</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p>But if we ask ChatGPT &apos;<em>how many days of annual leave am I entitled to?</em>&apos;, it will give you a generic, long-winded answer. It&apos;s not trained on our enterprise data and it doesn&apos;t have access to our proprietary information such as our policies. Therefore, it can never give a precise and concise answer. So how do we go from a generic response, to a precise response ?</p><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024--1-_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024--1-.mp4" poster="https://img.spacergif.org/v1/1706x866/0a/spacer.png" width="1706" height="866" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024--1-_thumb.jpg&apos;) 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:11</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1&#xD7;</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p>There are 3 main options here, but the most efficient of them all is Retrieval-Augmented Generation. So we finally get to it! So, what is RAG?</p><div class="kg-card kg-header-card kg-v2 kg-width-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="retrievalaugmented-generation" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Retrieval-Augmented Generation</span></h2>
                    <p id="its-in-the-name" class="kg-header-card-subheading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">It&apos;s in the name!</span></p>
                    
                </div>
            </div>
        </div><p>RAG is a framework that (1) <strong>Retrieves</strong> relevant, context-specific data from a knowledge-base in real-time, (2) <strong>augments</strong> the LLMs input with this retrieved data to (3) <strong>generate</strong> a response that is informed, contextually accurate and grounded upon up-to-date data. Easy-peasy! This framework addresses some of the key challenges you find with off the shelf LLMs</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image-1.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="965" height="584" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/12/image-1.png 600w, https://highoncloud.co.uk/content/images/2024/12/image-1.png 965w" sizes="(min-width: 720px) 720px"></figure><p>So now let&apos;s look at an actual (basic) RAG architecture!</p><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024--2-_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024--2-.mp4" poster="https://img.spacergif.org/v1/1658x914/0a/spacer.png" width="1658" height="914" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://highoncloud.co.uk/content/media/2024/12/Microsoft-PowerPoint---PowerPoint-Slide-Show----RAG2Realityv0.5----13-December-2024--2-_thumb.jpg&apos;) 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:10</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1&#xD7;</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p>For ease of understanding, let&apos;s split this into 2 sections; <strong>Data Ingestion</strong> and <strong>Query Response</strong>.</p><h2 id="data-ingestion">Data Ingestion</h2><p>Data Ingestion has 3 main steps.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image-6.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="823" height="859" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/12/image-6.png 600w, https://highoncloud.co.uk/content/images/2024/12/image-6.png 823w" sizes="(min-width: 720px) 720px"></figure><ol><li><strong>Chunking</strong></li></ol><p>Chunking is the process of dividing the corpus of documents into smaller text segments. It is required due to LLMs having a limit on the amount of data, typically measured in tokens, that it can in-take in any given call. So we split large texts into smaller segments, but an issue arrises when a given chunk doesn&apos;t capture an entire thought or idea.</p><p>In the RAG process, we will retrieve &apos;chunks&apos; based on a similarity search and it is these chunks that will be passed into the LLM. So if a &apos;chunk&apos; ends part-way through a thought, it may result in an incorrect conclusion as you are reading only part of an idea and you do not have the context in which to understand the given matching phrase in the chunk. How do we overcome this ? One popular method is overlapping; we overlap chunks such that a given text segment may be included in more than 1 chunk, this increases the chances of an entire thought or idea being captured in a single chunk. A more advanced technique is to use to use context-aware chunking in which an NLP model can be utilised to create semantic coherent chunks.</p><ol start="2"><li><strong>Embedding</strong></li></ol><p>Embedding is the process of transforming these text chunks into vectors, using an embedding model.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image-2.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="573" height="237"></figure><p>Embedding models are specially trained algorithms that convert the original data into a vector, whilst encapsulating information, features/properties of the original data in the vector. A vector consists of a list of numbers, each number is referred to as a dimension, and the dimensions capture the semantic meaning of the original word(s)/sentence(s) (when embedding text). The above image is a massive over simplification, but it is illustrating the idea that vectors are indeed a numeric representation of the information &amp; features of the original data. Vectors will be discussed in more detail in &apos;Semantic Search&apos; section</p><ol start="3"><li><strong>Vector Store</strong></li></ol><p>A vector store is just that, a store of all of the vectors. Vector stores are designed for low-latency retrievals and searching, whilst providing scalability and integration with popular ML pipelines for large-scale deployments</p><h2 id="query-response">Query Response</h2><p>Interacting with a RAG system (asking a question), and getting a response back, involves 5 key steps;</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image-8.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="1069" height="873" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/12/image-8.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/12/image-8.png 1000w, https://highoncloud.co.uk/content/images/2024/12/image-8.png 1069w" sizes="(min-width: 720px) 720px"></figure><ol><li><strong>Question to RAG System</strong></li></ol><p>We require an entry point into our RAG system which, in the diagram, is the &apos;Multi-turn Chat Application&apos;. It is worth noting, that both for data ingestion and query response, there several steps required, as such we require an orchestrator or co-ordinator, which isn&apos;t depicted in this basic RAG diagram. We&apos;ll highlight this further in the 3rd blog in this series.</p><ol start="2"><li><strong>Embed Question</strong></li></ol><p>The original question is embedded (converted to a vector), which will allow us to search against our vector store to find semantically similar documents. Embedding has already been covered in Data Ingestion.</p><ol start="3"><li><strong>Semantic Search</strong></li></ol><p>We then use the embedded question to execute an semantic search and identify the closest matching document chunks (which in theory will answer our question). The results of the semantic search will be an input into the LLM. But what exactly is semantic search ?</p><p>To try and avoid the philosophical debate about semantic search and vector similarity search, I will simply say that vector search is a part of a multi-step process to implement semantic search. Vector search refers to finding closely related data, in terms of semantics or features, by comparing the distance or angle of their vectors.</p><p>To understand this better, we&apos;ll base our example on Euclidean distance. Lets take the vector representation of a series of words and plot them onto a graph. The position of each word is represented by their respective vectors and through this we can visually see how closely related words are positioned close together. (In reality, the actual vectors would be hundreds of dimensions and the vectors would be plotted in a Euclidean space consisting of n-spaces, with n being the number of dimensions in a given vector. But for visual purposes, we&apos;ll represent them in 2 dimensions).</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image-3.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="684" height="373" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/12/image-3.png 600w, https://highoncloud.co.uk/content/images/2024/12/image-3.png 684w"></figure><p>A <em>raspberry</em> is very similar, in terms of semantics and features, to a <em>blackberry</em> and therefore they are positioned close together. A blackberry has quite different properties to a plum, but they are still related in the sense that both are fruits, as such they fall within the vicinity of all fruits. But a plum is very different to an elephant, so their vectors are of great distance.</p><p>So if we take a new word, we can find the closest matching words by simply comparing its vectors to the vectors in the series and seeing which is the closest.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image-4.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="836" height="458" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/12/image-4.png 600w, https://highoncloud.co.uk/content/images/2024/12/image-4.png 836w" sizes="(min-width: 720px) 720px"></figure><p>So if we insert the word <em>puppy</em>, we can see that <em>dog</em>, <em>cat</em> and <em>wolf</em> are the closest. But how do we do this mathematically ? We simply apply a formula to compare 2 vectors, the output being a single digit which numerically represents the distance between 2 given vectors.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/12/image-5.png" class="kg-image" alt="RAG to Reality: What is Retrieval-Augmented Generation ?" loading="lazy" width="617" height="517" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/12/image-5.png 600w, https://highoncloud.co.uk/content/images/2024/12/image-5.png 617w"></figure><p>The above example measures the euclidean distance between 2 vectors (derived from Pythagoras theorem), but there are many other formulas we can use. We simply;</p><ul><li>Calculate the difference between our 2 vectors over a given dimension</li><li>Square this number (this way we always work with a positive number and escape the void of imaginary numbers when square-rooting)</li><li>Do the above for all dimensions, and sum all outputs</li><li>Square root the sum to produce a single number which represents the distance of 2 vectors.</li></ul><p>Simply by looking at the numbers in the image above, we can tell that the top vector in the embedded data set is most like our vector as they are numerically the most similar!</p><ol start="4"><li><strong>Context</strong></li></ol><p>We then augment the input of the LLM with several pieces;</p><ul><li>The question itself</li><li>The semantic search results</li><li>Chat history</li><li>Pre-amble/instructions to the LLM, such as &apos;You job is to answer the given question based upon the data provided to you&apos;</li></ul><ol start="5"><li><strong>Response</strong></li></ol><p>The LLM will then generate a response, and with the augmented input the LLM should give us an accurate, up-to-date and contextually appropriate response</p><p></p><p>And that&apos;s the flow of a basic RAG architecture! We can take a brief look at how we can turn it from basic to advanced, but we&apos;ll but we&apos;ll save the details for another blog post!</p><div class="kg-card kg-header-card kg-v2 kg-width-wide " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="reliable-rag" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Reliable RAG</span></h2>
                    <p id="rag-enhancements" class="kg-header-card-subheading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">RAG Enhancements</span></p>
                    
                </div>
            </div>
        </div><p>Just a quick overview of functionality we can add to improve the reliability of our basic RAG architecture! We&apos;ll cover these in more detail in another blog.</p><ul><li>Query Translation</li></ul><p>You can&apos;t always trust the user to ask the right question, or ask it in the right way. If the question is too specific, then the semantic search may miss chunks that are relevant, if the question is too broad you may involve less relevant chunks which could skew the answer. There are many ways to address this; below are just 3;</p><ol><li>Re-write the query</li><li>Create several alternatives, run several queries simultaneously and combine the results</li><li>HyDE - Hypothetical Document Embedding: Have GenAI produce a hypothetical document that may address the question, and execute the semantic search for that document</li></ol><ul><li>Hallucination Checker</li></ul><p>Use a separate checker (a dedicated LLM/prompt/agent) whose job is to read the draft answer and the retrieved context and flag unsupported claims. The flow is simple: generate &#x2192; check &#x2192; (optionally) revise or return &quot;no grounded answer.&quot; This keeps the answering model fast while the checker enforces groundedness. In practice you&#x2019;ll score entailment/faithfulness against the retrieved passages and only ship when confidence is high.</p><ul><li>Re-Ranking</li></ul><p>Start broad, then sharpen. Do a fast first-pass retrieval, then apply a cross&#x2011;encoder re&#x2011;ranker to the top candidates so the best 5&#x2013;10 rise to the top. On Oracle, you can call Cohere Rerank via OCI Generative AI as a managed re&#x2011;rank model. The result is higher precision without slowing down ingestion or indexing.</p><ul><li>Retrieval Fusion</li></ul><p>Don&#x2019;t bet on a single retriever. Combine keyword search (BM25) with vector similarity (AI Vector Search in Oracle Database 23ai) and fuse the ranked lists, e.g., with Reciprocal Rank Fusion (RRF). This gives you the best of both!</p><ul><li>Routing</li></ul><p>Not every query needs the same path. Add a lightweight router that decides: which collection to hit, whether to use keyword, vector, or both, whether to expand the query, or to skip retrieval entirely. In Oracle, Select AI with RAG lets you keep this logic close to the data, and OCI Generative AI Agents (RAG) can orchestrate multi&#x2011;source workflows across enterprise content.</p><ul><li>GraphRAG</li></ul><p>Graphs allow you to connect pieces of data together, so you can take a large document, split it into chunks and then connect the chunks. This connection shows a relationship between elements that may not be semantically similar. For example with Harry Potter novel, you may connect Harry Potter with; Hogwarts, Gryffindor, Orphan etc, The idea here is that you still do a vector search to identify semantically related chunks, but then you include related facts that are highly relevant in formulating a response but are not semantically connected...</p><ul><li>Multi&#x2011;modal</li></ul><p>Often your documents aren&apos;t just text, they may have embedded images, tables,  scanned text etc and different modalities (image, audio, text, video). To make them searchable, we need to ensure we can embed, search and &apos;understand&apos; the various formats by utilising multi-modal LLMS (to do, for example OCR) and allow us to execute the same RAG flow on these documents.</p>]]></content:encoded></item><item><title><![CDATA[OCI LoadBalancer - Free SSL & Autorenewal (Let's Encrypt & Certbot)]]></title><description><![CDATA[<p></p><p>In this article we&apos;ll look at setting up SSL Certificates for free on OCI LoadBalancer (LB), as well as automatically renewing the certificates in the LB when they expire. We&apos;ll use Terraform to do all of our Infrastructure setup, so we can focus on the Certificates</p>]]></description><link>https://highoncloud.co.uk/oci-lb-free-ssl-cert/</link><guid isPermaLink="false">6597e148aec486e263abf63d</guid><dc:creator><![CDATA[Shahvaiz Janjua]]></dc:creator><pubDate>Tue, 13 Feb 2024 10:00:26 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2022/08/arch-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2022/08/arch-1.png" alt="OCI LoadBalancer - Free SSL &amp; Autorenewal (Let&apos;s Encrypt &amp; Certbot)"><p></p><p>In this article we&apos;ll look at setting up SSL Certificates for free on OCI LoadBalancer (LB), as well as automatically renewing the certificates in the LB when they expire. We&apos;ll use Terraform to do all of our Infrastructure setup, so we can focus on the Certificates &amp; Automation!</p><h2 id="components">Components</h2><h3 id="oracle-cloud-infrastructure-load-balancer">Oracle Cloud Infrastructure Load Balancer</h3><p>The OCI Load Balancer provides Layer 7 Load Balancing and TLS Support, allowing us to use it for SSL termination, unlike the OCI Network Load Balancer (which does not provide either). This allows us to have a secure connection between End User and the Load Balancer, and then a HTTP call (stripping out SSL) between the Load Balancer and application, which is what we&apos;ll be setting up in this Tutorial. You can also do SSL Tunneling &amp; End-2-End SSL.</p><h3 id="oracle-cloud-infrastructure-command-line-interface">Oracle Cloud Infrastructure Command Line Interface</h3><p>The CLI is a small-footprint tool that you can use on its own or with the&#xA0;Console&#xA0;to complete&#xA0;Oracle Cloud Infrastructure&#xA0;tasks. The CLI provides the same core functionality as the Console, plus additional commands. Some of these, such as the ability to run scripts, extend&#xA0;Console&#xA0;functionality.</p><h3 id="lets-encrypt">Let&apos;s Encrypt</h3><p>Let&apos;s Encrypt is a free, opensource and automated Certificate Authority. Let&apos;s Encrypt offer FREE SSL/TLS Certificates! They are only valid for 90 days, but can simply be renewed.</p><h3 id="certbot">Certbot</h3><p>Certbot is a free, open source software tool for automatically using&#xA0;Let&#x2019;s Encrypt&#xA0;certificates on manually-administrated websites to enable HTTPS.</p><p></p><h2 id="architecture">Architecture</h2><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2022/08/arch.png" class="kg-image" alt="OCI LoadBalancer - Free SSL &amp; Autorenewal (Let&apos;s Encrypt &amp; Certbot)" loading="lazy" width="468" height="315"></figure><p></p><p>The architecture shown above is a simplified workflow of the SSL Certificate renewal &amp; deployment. Everything in red pertains to the Certificate obtainment and deployment; Certbot VM is dedicated to this process. The LoadBalancer listens on 2 ports, each of which are connected to a different backend.</p><p>The first Listening Port is 443. This is the User TLS traffic that uses the Certificate. SSL Termination occurs on the LB, the traffic is then forwarded to a Compute instance, hosting a Webserver running on port 2839</p><p>The second Listening Port is 80. When traffic hits the LoadBalancer on Port 80, the LB forwards this traffic to Certbot VM on port 80, this is required for the Certificate creation</p><p>Let&apos;s Encrypt functions as an ACME Server and Certbot is an ACME client. In this setup ACME HTTP challenge is used, with this the ACME Server will attempt to access http://&lt;DOMAIN&gt;/.well-known/acme-challenge/&lt;TOKEN&gt; in order to prove ownership of the domain and create the Certificate. This means that the Certbot VM has to be accessible over port 80. Therefore our LB will be listening on port 80 and will direct the traffic to Certbot since the Compute instance is not accessible to Public Internet and has to be accessed via LB.</p><p>The high level flow is as follows;</p><ul><li>Certbot stands up Webserver running on port 80, for ACME Challenge</li><li>Certbot fetches certificate from Let&apos;s Encrypt</li><li>Let&apos;s Encrypt validates domain ownership via ACME HTTP Challenge</li><li>New Certificate created and obtained from Certbot</li><li>Certbot Hook calls custom script</li><li>Custom script uses OCI CLI to upload the new certificate to LB &amp; update LB Listener to use the new certificate</li></ul><p></p><h2 id="pre-requisites">Pre-Requisites</h2><ul><li>Oracle Cloud Infrastructure Tenancy so you can access <a href="https://www.oracle.com/cloud/free/?ref=highoncloud.co.uk">Always Free</a> services</li><li>Domain with DNS Entry (Can be obtained for free via <a href="https://www.noip.com/?ref=highoncloud.co.uk">NoIP</a>)</li></ul><div class="kg-card kg-header-card kg-width-full kg-size-small kg-style-dark" data-kg-background-image style><h2 class="kg-header-card-header" id="infrastructure-setup"><span style="white-space: pre-wrap;">Infrastructure Setup</span></h2><h3 class="kg-header-card-subheader" id="terraform"><span style="white-space: pre-wrap;">Terraform</span></h3></div><p>All of the terraform code is found here. Simply run this (don&apos;t forget to pass in your environment variables). This section is simply to give you an understanding of what we&apos;re doing via these terraform scripts.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/ShahvaizJanjua/HOC_FREE_SSL_Demo?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - ShahvaizJanjua/HOC_FREE_SSL_Demo</div><div class="kg-bookmark-description">Contribute to ShahvaizJanjua/HOC_FREE_SSL_Demo development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="OCI LoadBalancer - Free SSL &amp; Autorenewal (Let&apos;s Encrypt &amp; Certbot)"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">ShahvaizJanjua</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/e0352ad416eafb6a5b7804014f9e767fc18d3a334c6983cfe3160f360dbec966/ShahvaizJanjua/HOC_FREE_SSL_Demo" alt="OCI LoadBalancer - Free SSL &amp; Autorenewal (Let&apos;s Encrypt &amp; Certbot)"></div></a></figure><p></p><p>Let&apos;s take a look at what the Terraform code will deploy.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/02/tarch.png" class="kg-image" alt="OCI LoadBalancer - Free SSL &amp; Autorenewal (Let&apos;s Encrypt &amp; Certbot)" loading="lazy" width="421" height="659"></figure><p>Lets touch on 3 Components here;</p><ol><li>LoadBalancer Health Check:<br>The Loadbalancer sends traffic to the Certbot VM on port 80, however for the health check we use Port 22.<br><em>The reason for using Port 22 for the healthcheck is because the LoadBalancer will only pass traffic to the Compute instance if there is a service listening on the port and it gives a valid response, it performs this check at regular intervals. However the service that uses Port 80 is only started when the certbot is executed, it then immediately performs the ACME Challenge which will call Port 80. When the webservice starts, the LoadBalancer will get a valid response on its next health check, however the gap between starting webservice on port 80 and issueing the ACME challenge is too short for the LB to detect a valid backend and therefore the LB will not pass on the the ACME challenge to the backend/Compute instance. Port 22 (SSH) is always running and so the backend will always be healthy, ensuring the ACME Challenge traffic is passed to the Compute instance</em></li><li>Service Gateway:<br>This is required in order to use the Compute agent plugins, namely Bastion plugin, which will allow us to use the Bastion service</li><li>Bastion Service:<br>The LB Subnet is public, therefore accessible via Internet Gateway. However since the App Subnet is private, we&apos;ll be using the Bastion service in order to access the private VMs </li><li>Web Server:<br>This tutorial is about configuring Certbot, so we&apos;ll do the Web Server setup behind the scenes via terraform. We&apos;ll use cloud-init to setup the web server automatically upon the VM first starting so we don&apos;t have to spend time on this.</li></ol><p></p><p>There&apos;s a few parts to performing the Infrastructure setup, the terraform scripts are relatively standard and simple, so we&apos;ll touch on some of the interesting parts, but this isn&apos;t a terraform tutorial so feel free to skip over this section!</p><h3 id="computetf">Compute.tf</h3><p>We&apos;ll use cloud-init to run a custom script on the Web Server to automatically configure our Web Service. All we want is a simple html page and a HTTP server running on port 8080 (instead of installing a HTTP server, we&apos;ll use the http.server python module to standup a basic python HTTP Service, purely for demo purposes). We can define the script by having the following data resource in terraform</p><pre><code class="language-terraform">data &quot;template_file&quot; &quot;cloud-config&quot; {
  template = &lt;&lt;YAML
#cloud-config
runcmd:
 - echo &apos;Starting Setup&apos; &gt;&gt; /tmp/setup.log
 - sudo systemctl stop firewalld
 - mkdir -p /var/www/html
 - wget --output-document=/var/www/html/index.html https://github.com/ShahvaizJanjua/HOC_FREE_SSL_Demo/blob/main/index.html
 - cd /var/www/html
 - nohup python -m http.server 8080 &amp;
YAML
}</code></pre><p>To have this initialisation script executed, we base64-encode the data resource and assign it to user_data variable under the metadata block of WebServer oci_core_instance</p><pre><code>  metadata = {
    ssh_authorized_keys = file(var.public_key_path)
    user_data = &quot;${base64encode(data.template_file.cloud-config.rendered)}&quot;
  }</code></pre><p>Since our VMs are in a private subnet, we&apos;ll be using the free Bastion service to access the VMs. In order to do this, the Compute instances require to have the Bastion Plugin enabled, which we can do using the optional <em>agent_config</em> parameter</p><pre><code class="language-terraform"> agent_config {
        plugins_config {
            desired_state = &quot;ENABLED&quot;
            name = &quot;Bastion&quot;
        }
    }</code></pre><h3 id="networktf">Network.tf</h3><p>We require to ensure we have the Service Gateway in our route table for the App Subnet, this is to allow us to enable the Bastion Plugin</p><pre><code class="language-terraform">resource &quot;oci_core_route_table&quot; &quot;app_rt&quot; {
    compartment_id = oci_identity_compartment.demo.id
    vcn_id = oci_core_vcn.lb_demo_vcn.id
    display_name = &quot;APP_RT&quot;
    route_rules {
        network_entity_id = oci_core_service_gateway.service_gateway.id
        destination = &quot;all-fra-services-in-oracle-services-network&quot;
        destination_type = &quot;SERVICE_CIDR_BLOCK&quot;
    }

    route_rules {
        network_entity_id = oci_core_nat_gateway.nat_gateway.id
        destination = &quot;0.0.0.0/0&quot;
        destination_type = &quot;CIDR_BLOCK&quot;
    }
}</code></pre><h3 id="networksecuritygroupstf">NetworkSecurityGroups.tf</h3><p>We want to allow incoming traffic to the LB on both ports 80 &amp; 443. We can define both ports in a single resource block instead of having multiple. We do this by using <em>for_each</em> with a set of ports, which are then passed into the <em>source_port_range</em> block using <em>each.key</em></p><pre><code class="language-terraform">resource &quot;oci_core_network_security_group_security_rule&quot; &quot;lb_nsg_ingress&quot; {
    network_security_group_id = oci_core_network_security_group.lb_nsg.id
    direction = &quot;INGRESS&quot;
    protocol = 6

    description = &quot;Allow Public Access&quot;
    source = &quot;0.0.0.0/0&quot;
    source_type = &quot;CIDR_BLOCK&quot;
    for_each = toset([&quot;80&quot;,&quot;443&quot;])

    tcp_options {
        source_port_range {
            max = each.key
            min = each.key
        }
    }
}</code></pre><p> We want the LB to be able to communicate to the VMs on their respective ports. We use NSGs of each VM as the destination, which gives us flexibility and avoids hard-coding in IPs. We can use key value pairs in <em>for_each</em> in order to pass the port with its associated destination NSG.</p><pre><code class="language-terraform">resource &quot;oci_core_network_security_group_security_rule&quot; &quot;lb_nsg_egress&quot; {
    depends_on = [
      oci_core_network_security_group.crtbot_nsg,
      oci_core_network_security_group.websrvr_nsg
    ]
    network_security_group_id = oci_core_network_security_group.lb_nsg.id
    direction = &quot;EGRESS&quot;
    protocol = 6

         for_each = {&quot;80&quot; : oci_core_network_security_group.crtbot_nsg.id,
    &quot;22&quot; : oci_core_network_security_group.crtbot_nsg.id,
     &quot;8080&quot; : oci_core_network_security_group.websrvr_nsg.id}

    description = &quot;Access VMs&quot;
    destination = each.value
    destination_type = &quot;NETWORK_SECURITY_GROUP&quot;
    
    tcp_options {
        destination_port_range {
            max = each.key
            min = each.key
        }
    }
}</code></pre><p></p><h3 id="bastiontf">Bastion.tf</h3><p>To create a Bastion session, we first require to provision a Bastion service and specify the target subnet &amp; allows IP address range</p><pre><code class="language-terraform">resource &quot;oci_bastion_bastion&quot; &quot;bastion&quot; {
    bastion_type = &quot;STANDARD&quot;
    compartment_id = oci_identity_compartment.demo.id
    target_subnet_id = oci_core_subnet.vm_sub.id
    client_cidr_block_allow_list = [&quot;0.0.0.0/0&quot;]
}</code></pre><p>In order to use the Bastion Service, we need to ensure that the Bastion Plugin is enabled on the Compute Instance. This takes a short time to instantiate and be detected as running, therefore we want a wait/sleep after provisioning the CertBotVM before we provision the Bastion Session, lets setup a wait/sleep resource that will wait for 5 minutes from when the CertBotVM instance is created.</p><pre><code class="language-terraform">resource &quot;time_sleep&quot; &quot;wait_5_minutes_for_bastion_plugin&quot; {
  depends_on = [oci_core_instance.CertBotVM]
  create_duration = &quot;5m&quot;
}</code></pre><p>We then provision a new Bastion Session, it will depend on the wait resource and as such will wait for 5 minutes from CertBotVM creation before provisioning the Bastion Session.</p><pre><code class="language-terraform">resource &quot;oci_bastion_session&quot; &quot;session&quot; {
    bastion_id = oci_bastion_bastion.bastion.id
    
    key_details {
        public_key_content = file(var.public_key_path)
    }
    
    target_resource_details {
        session_type = &quot;MANAGED_SSH&quot;
        target_resource_id = oci_core_instance.CertBotVM.id
        target_resource_operating_system_user_name = &quot;opc&quot;
        target_resource_port = &quot;22&quot;
    }

    session_ttl_in_seconds = &quot;10800&quot;
    depends_on = [time_sleep.wait_5_minutes_for_bastion_plugin]
}</code></pre><p></p><div class="kg-card kg-header-card kg-width-full kg-size-small kg-style-dark" data-kg-background-image style><h2 class="kg-header-card-header" id="installation-amp-configuration-"><span style="white-space: pre-wrap;">Installation &amp; Configuration </span></h2><h3 class="kg-header-card-subheader" id="certbot-automatic-renewal-amp-oci-cli"><span style="white-space: pre-wrap;">Certbot, Automatic Renewal &amp; OCI CLI</span></h3></div><h3 id="noip">NoIP</h3><p>We first grab a free domain from <a href="http://www.noip.com/?ref=highoncloud.co.uk" rel="noreferrer">NoIP.com</a></p><p>Ensure you have added an A record to the DNS on NoIP to point your domain to the public IP of the Load Balancer</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/01/image-12.png" class="kg-image" alt="OCI LoadBalancer - Free SSL &amp; Autorenewal (Let&apos;s Encrypt &amp; Certbot)" loading="lazy" width="990" height="811" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-12.png 600w, https://highoncloud.co.uk/content/images/2024/01/image-12.png 990w" sizes="(min-width: 720px) 720px"></figure><p></p><h3 id="install-certbot">Install Certbot</h3><p>When we run the terraform scripts, it will provide us with the SSH string to be able to connect to the certbot VM via bastion.</p><p>Now lets install Certbot</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">sudo dnf config-manager --enable ol8_developer_EPEL</code></pre><figcaption><p><span style="white-space: pre-wrap;">Enable Developer EPEL in order to install snapd</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">sudo dnf install snapd -y</code></pre><figcaption><p><span style="white-space: pre-wrap;">Install snapd</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">sudo setenforce 0
sudo systemctl stop firewalld</code></pre><figcaption><p><span style="white-space: pre-wrap;">Disable selinux</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">sudo ln -s /var/lib/snapd/snap /snap</code></pre><figcaption><p><span style="white-space: pre-wrap;">Create a symbolic link to /snap</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">sudo systemctl enable snapd
sudo systemctl start snapd</code></pre><figcaption><p><span style="white-space: pre-wrap;">Start snapd</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">sudo snap install --classic certbot</code></pre><figcaption><p><span style="white-space: pre-wrap;">Install certbot</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">sudo ln -s /snap/bin/certbot /usr/bin/certbot</code></pre><figcaption><p><span style="white-space: pre-wrap;">Create a symbolic link to easily use certbot</span></p></figcaption></figure><h3 id="install-oci-cli">Install OCI CLI</h3><p>We&apos;ll be using OCI CLI to automatically deploy the new certificate once it is generated and every time it is renewed, so let&apos;s install OCI CLI.</p><pre><code class="language-shell">sudo dnf install python36-oci-cli -y</code></pre><figure class="kg-card kg-code-card"><pre><code class="language-shell">oci setup config</code></pre><figcaption><p><span style="white-space: pre-wrap;">Run this as opc and also run with sudo as well, or copy /home/opc/.oci/config to /root/.oci/</span></p></figcaption></figure><p></p><h3 id="automatic-renewal">Automatic Renewal</h3><p>Before we obtain our free certificate, lets setup our custom script to auto deploy the certificate to the Load Balancer. In older versions of certbot a cronjob would be required, however now certbot automatically schedules a (systemd) Timer to execute the certbot renew service. You can view this via <em>sudo systemctl list-timers</em></p><p>There&apos;s 2 ways of having a script invoked via certbot renewal;</p><ol><li>Pass in <em>--deploy-hook &lt;script&gt;</em> when running certbot command. <br>A config file, named &lt;DOMAIN&gt;.conf will be created in /etc/letsencrypt/renewal. This config file will have an entry pointing to our custom script which will be executed whenever a renewal is run.</li><li>Place a script in <em>/etc/letsencrypt/renewal-hooks/deploy</em><br>All scripts in here will be automatically executed when certbot executes a succesful certificate renewal</li></ol><p>We&apos;ll go with option 1. Create a simple script like below</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">oci lb certificate create \
--load-balancer-id ocid1.loadbalancer.oc1.eu-frankfurt-1.aaaaaaaace2yjn77hmilofdqe5qcjqz5qomgoiekiymjuzjokvk5te6kxl2a \
--wait-for-state SUCCEEDED \
--certificate-name &quot;$RENEWED_DOMAINS-&quot;`date +&quot;%Y-%m-%d&quot;` \
--ca-certificate-file &quot;$RENEWED_LINEAGE/fullchain.pem&quot; \
--private-key-file &quot;$RENEWED_LINEAGE/privkey.pem&quot; \
--public-certificate-file &quot;$RENEWED_LINEAGE/cert.pem&quot;

oci lb listener update --force \
--load-balancer-id ocid1.loadbalancer.oc1.eu-frankfurt-1.aaaaaaaace2yjn77hmilofdqe5qcjqz5qomgoiekiymjuzjokvk5te6kxl2a \
--wait-for-state SUCCEEDED \
--listener-name &quot;Web_Listener&quot; \
--default-backend-set-name &quot;WebBackendSet&quot; \
--port 443 --protocol HTTP2 --cipher-suite-name oci-default-http2-ssl-cipher-suite-v1 \
--ssl-certificate-name &quot;$RENEWED_DOMAINS-&quot;`date +&quot;%Y-%m-%d&quot;`</code></pre><figcaption><p><span style="white-space: pre-wrap;">lbcertupload.sh</span></p></figcaption></figure><p></p><h3 id="obtain-certificate-automatically-deploy">Obtain Certificate &amp; Automatically Deploy</h3><p>Now we can generate the SSL certificates</p><pre><code class="language-shell">[opc@crtbot ~]# sudo certbot certonly --standalone --deploy-hook /home/opc/lbcertupload.sh
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Please enter the domain name(s) you would like on your certificate (comma and/or
space separated) (Enter &apos;c&apos; to cancel): [lbdemo.ddns.net](http://lbdemo.ddns.net/)
Requesting a certificate for [lbdemo.ddns.net](http://lbdemo.ddns.net/)

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/lbdemo.ddns.net/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/lbdemo.ddns.net/privkey.pem
This certificate expires on 2023-05-03.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

---

If you like Certbot, please consider supporting our work by:

- Donating to ISRG / Let&apos;s Encrypt: [https://letsencrypt.org/donate](https://letsencrypt.org/donate)
- Donating to EFF: [https://eff.org/donate-le](https://eff.org/donate-le)

---</code></pre><p></p><p>This will retrieve the certificate and execute our custom script which will upload it to OCI and update the Listener to use the new certificate</p><p></p><p>Lets go to our site and checkout the new certificate in action!</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/01/image-13.png" class="kg-image" alt="OCI LoadBalancer - Free SSL &amp; Autorenewal (Let&apos;s Encrypt &amp; Certbot)" loading="lazy" width="1129" height="608" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-13.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/01/image-13.png 1000w, https://highoncloud.co.uk/content/images/2024/01/image-13.png 1129w" sizes="(min-width: 720px) 720px"></figure><h2 id></h2>]]></content:encoded></item><item><title><![CDATA[Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]]]></title><description><![CDATA[<p></p><p>In this article you&apos;ll find the full terraform script to spin up &amp; configure the end-2-end architecture, a long with a selective walkthrough of the main terraform code through snippets, finished off with a networking explanation pertaining to the main OCI components</p><p>We are effectively using the methodology</p>]]></description><link>https://highoncloud.co.uk/advanced-networking-drg/</link><guid isPermaLink="false">63226d5d0e2f0a35f98f43e3</guid><category><![CDATA[Advanced]]></category><dc:creator><![CDATA[Shahvaiz Janjua]]></dc:creator><pubDate>Tue, 30 Jan 2024 10:00:51 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2024/02/hub-and-spoke-drg.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2024/02/hub-and-spoke-drg.png" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]"><p></p><p>In this article you&apos;ll find the full terraform script to spin up &amp; configure the end-2-end architecture, a long with a selective walkthrough of the main terraform code through snippets, finished off with a networking explanation pertaining to the main OCI components</p><p>We are effectively using the methodology explained in the <a href="https://docs.oracle.com/en-us/iaas/Content/Network/Tasks/scenario_g.htm?ref=highoncloud.co.uk">Using a DRG to route traffic through a centralized network virtial appliance</a>. The referenced documentation explains the entire setup, the concepts involved and the traffic flow &amp; routing. Our architecture is a slight modification for a scenario requested by a customer, in which traffic from 1 destination follows the referenced flow but other traffic bypasses the centralized network firewall and is routed directly to the end target. The entire setup is done in Terraform. The terraform scripts also mimics customer connectivity (via IPSec tunnel) using Libreswan, all of which the terraform scripts configure with the help of Ansible, but more on that later.</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/ShahvaizJanjua/HOC_DRG-NFW-TF-Demo?ref=highoncloud.co.uk"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - ShahvaizJanjua/HOC_DRG-NFW-TF-Demo</div><div class="kg-bookmark-description">Contribute to ShahvaizJanjua/HOC_DRG-NFW-TF-Demo development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">ShahvaizJanjua</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/619e4f66aa784f742f445d078d7a07098e1c6a87b90e6edff782484d6a26238b/ShahvaizJanjua/HOC_DRG-NFW-TF-Demo" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]"></div></a><figcaption><p><span style="white-space: pre-wrap;">All the code in this blog can be found here</span></p></figcaption></figure><p>We&apos;ll cover the following;</p><ol><li>Architecture</li><li>OCI Networking Concepts</li><li>Terraform scripts involved</li><li>Terraform code walkthrough</li><li>Traffic Flow &amp; Route Table Explanation (Bonus)</li></ol><p></p><h2 id="architecture">Architecture</h2><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="1602" height="908" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2023/01/image.png 1600w, https://highoncloud.co.uk/content/images/2023/01/image.png 1602w" sizes="(min-width: 720px) 720px"></figure><p>Our setup has the following components &amp; requirements;</p><ol><li>Hub VCN, containing an OCI Network Firewall</li><li>A Spoke VCN which has the customer OCI workload, represented through a single Compute Instance</li><li>2 separate IPSec tunnels, each connecting to a different site</li><li>All traffic from Workload VM to &apos;On-Premise&quot; site must pass through the firewall via the DRG and then be routed to the DRG and down the IPSec tunnel for &apos;On-Premises&apos; and vice versa - represented with the blue highlight</li><li>All traffic from Workload VM to &apos;Multicloud&apos; site will go directly to the &apos;Multicloud&apos; IPSec tunnels via the DRG, bypassing the Network Firewall - represented with the green highlight</li><li>&apos;On-Premises&apos; and &apos;Multicloud&apos; sites are represented by the following setup;<br>VCN created for each site, containing 2 subnets. 1 Subnet for the CPE and 1 Subnet to represent customer workload/vm&apos;s<br>CPE is represented via Libreswan, a free IKE implementation for Linux, which is installed on a Compute Instance</li><li>Traffic Logging enablement for Network Firewall to confirm the required traffic is passing through the firewall</li></ol><p></p><h2 id="oci-networking-concepts">OCI Networking Concepts</h2><p>Lets take a look at a couple of OCI Networking Concepts;</p><h3 id="drg-attachment">DRG Attachment;</h3><p>Connects the Dynamic Routing Gateway to either a; Virtual Cloud Network, Virtual Circuit, IPSec or Remote Peering Connection. The DRG attachment allows the DRG to send traffic and receive traffic to &amp; fro the given attached resource.</p><p>Every DRG attachment has a DRG Route Table associated with it. This DRG Route Tables Rules are used to route traffic incoming to the DRG from the other end of the attachment (e.g. the IPSec in an IPSec Attachment).</p><p>VCN attachments can have 2 Route Tables; 1 DRG Route Table for traffic <em>incoming to the DRG from the VCN</em>, 1 VCN Route Table for traffic<em> incoming to the VCN from the DRG (</em>this differs from normal use of VCN Route Tables which route traffic leaving the Subnet<em>)</em></p><h3 id="dynamic-route-import-distribution">Dynamic Route Import Distribution</h3>
<p>Before understanding Dynamic Route Import Distributions, it is important to note that a DRG Route Table contains both Static Routes and Dynamic Routes. Static Routes Rules are manually added by us; however a Static Route can only have a Virtual Cloud Network, Remote Peering Connection or Cross-Tenancy as its next hop. So in order to route traffic to IPSecs or Virtual Circuits we require to produce the rules dynamically, which is where Dynamic Route Import Distributions comes into play.</p><p>An Import Route Distribution is associated with a DRG Route Table. It contains a set of statements which are criteria&apos;s from which DRG attachments to use to populate the dynamic route rules in the DRG route table. When an DRG attachment matches the criteria, all routes associated with the attachments are dynamically imported into the DRG Route Table; for example for a Virtual Circuit all routes advertised using BGP will be imported into the DRG Route Table with the next hop as the DRG attachment (i.e. the Virtual Circuit). The match criteria can be;</p><ol><li>Match All</li><li>Attachment Type - in which you specify the attachment type such as Virtual Circuit - results in all IPs advertised over BGP in ALL Virtual Circuits being dynamically added to the DRG Route Table</li><li>Attachment - in which you specify an individual Attachment, such as a single Virtual Circuit - results in only the IPs advertised over BGP for that specific Virtual Circuit to be dynamically added to the DRG Route Table</li></ol><p> </p><h2 id="terraform-scripts">Terraform Scripts</h2><p>Before we dive into the code, lets take a very quick high level look at the scripts;</p><ul><li>Variables.tf - Holds all of our variables; Network CIDRs default values set, can be modified as desired</li><li>Terraform.tfvars - To be populated, variables pertaining to terraform OCI authentication and keys</li><li>Compartments.tf - Holds the OCI provider block and creation of compartment under root compartment</li><li>Data.tf - Holds all Data Sources</li><li>Networking.tf - Setups all networking elements except DRG &amp; IPSec tunnels - i.e. VCNs, Subnets, VCN Route tables, Default Security List modification, Internet Gateway</li><li>NetworkFirewall.tf - Creation of OCI Network Firewall Policy &amp; OCI Network Firewall</li><li>Logging.tf - Enable Traffic Logs to be collected from Network Firewall</li><li>Compute.tf  - Creation of OnPrem CPE/Librewan, OnPrem Workload VM, Multicloud CPE/Librewan, Multicloud Workload VM, Spoke (OCI Workload) VM</li><li>DRG.tf - Creation of DRG, DRG Route Tables, DRG Route Table Import Distribution &amp; DRG Attachments</li><li>IPSec.tf - Creation of CPEs &amp; IPSecs. Updating; IPSec Tunnels Interface IPs &amp; IPSec Tunnel attachments DRG Route Table</li><li>LibreswanSetup.tf - Ansible integration to execute ansible playbook to setup Libreswan configuration</li><li>Ansible-vars.tf - Dynamically populate .yml file containing all of the values required by Libreswan config files</li><li>/ansible/multicloud-libswan.yml &amp; /ansible/onprem-libreswan.yml - Ansible scripts to; Install Libreswan, upload Libreswan config files, modify kernel parameters, start Libreswan, configure static routing</li><li>/ansible/multicloud-libreswan.j2 &amp; /andible/onprem-libreswan.j2 - Libreswan config file, populated from the file created dynamically by Ansible-vars.tf</li><li>/ansible/multicloud-libreswan_secrets - IPSec tunnels shared secret, populated from the file created dynamically by Ansible-vars.tf</li><li>vpn_vars not needed, add values to ansible-vars.tf, remove reference from .yml and delete</li></ul><h2 id="terraform-code">Terraform Code</h2><p>Lets get into the Code;</p><h3 id="networkingtf">Networking.tf</h3><p>Creation of VCNs, Subnets, Internet Gateway and population of Route Tables and Security Lists are quite straightforward. Ideally we would utilise Network Security Groups, but for the purposes of this Customer Demonstration we simply used the default Security Lists and opened them up to the world.</p><pre><code class="language-HCL">resource &quot;oci_core_default_security_list&quot; &quot;spoke_default_sl&quot; {
    manage_default_resource_id = oci_core_vcn.spoke_vcn.default_security_list_id
    compartment_id = oci_identity_compartment.demo.id

   ingress_security_rules {
    protocol = &quot;all&quot;
    source = &quot;0.0.0.0/0&quot;
   }

    egress_security_rules {
    protocol = &quot;all&quot;
    destination = &quot;0.0.0.0/0&quot;
   }
}</code></pre><p>You can modify the rules for the default security list by using the <em>manage_default_resource_id</em>. The <em>default security list id</em> is an attribute of a VCN resource, so we can obtain it directly from our VCN resource via <em>oci_core_vcn.spoke_vcn.default_security_list_id</em></p><p>Default Route table follows the same premise;</p><pre><code class="language-HCL">resource &quot;oci_core_default_route_table&quot; &quot;multicloud_default_route_table&quot; {
    manage_default_resource_id = oci_core_vcn.multicloud_vcn.default_route_table_id
    display_name = &quot;Default Route Table&quot;

    route_rules {
        network_entity_id = oci_core_internet_gateway.multicloud_igw.id
        destination = &quot;0.0.0.0/0&quot;
        destination_type = &quot;CIDR_BLOCK&quot;
    }
}</code></pre><p></p><h3 id="computetf">Compute.tf</h3><p>Compute Resource is also quite straight forward, the only interesting part is obtaining the Image ID which needs to be given in the source_details attribute of Compute resource</p><pre><code class="language-HCL">  source_details {
    source_type = &quot;image&quot;
    source_id   = data.oci_core_images.os.images[0].id
  }</code></pre><p>As you can see the Image ID is obtained from a Data Source, which gives us a list of images and we simply select the first element in the list. So lets take a look at the data source that this references</p><pre><code class="language-HCL">data &quot;oci_core_images&quot; &quot;os&quot; {
  compartment_id           = oci_identity_compartment.demo.id
  operating_system = &quot;Oracle Linux&quot;
  operating_system_version = &quot;8&quot;

  shape                    = &quot;VM.Standard.E4.Flex&quot;
  sort_by                  = &quot;TIMECREATED&quot;
  sort_order               = &quot;DESC&quot;
}</code></pre><p>We use the <em>oci_core_images</em> Data Source and specify the shape of our Compute to ensure we only get images compatible with our shape. We also want Oracle Linux 8 so we populate operating system details and are ordering by the newest created image.</p><p>We need to specify Public Key for SSH</p><pre><code class="language-HCL">  metadata = {
    ssh_authorized_keys = file(var.public_key_path)
  }</code></pre><p>We do this by referencing the variable that holds the path to our Public Key file and telling it that it is a file.</p><p></p><h3 id="drgtf">DRG.tf</h3><p>The DRG setup is mostly also straight forward, we require to do the following;</p><ol><li>Create Dynamic Routing Gateway</li><li>Create DRG Route Tables</li><li>Create DRG Route Table Rules (to populate DRG Route Tables)</li><li>Create Import Route Distribution</li><li>Create Import Route Distribution Statements (to populate Import Route Distribution)</li><li>Create DRG VCN attachments</li></ol><p></p><pre><code class="language-HCL">resource &quot;oci_core_drg_route_table_route_rule&quot; &quot;drg_multicloud_ipsec_rt_rule&quot; {
    drg_route_table_id = oci_core_drg_route_table.drg_multicloud_ipsec_rt.id
    destination = oci_core_vcn.spoke_vcn.cidr_block
    destination_type = &quot;CIDR_BLOCK&quot;
    next_hop_drg_attachment_id = oci_core_drg_attachment.spoke_drg_attachment.id
}</code></pre><p>For the rule, we specify the DRG Route Table ID for which the Rule is for, and then the Destination Type, followed by the Destination address and what the next hop is. Lets take a look at populating our Import Route Distribution; as with DRG Route Tables, you create the Import Route Distribution via a resource and then populate it via a separate Import Route Distribution Statement resource</p><pre><code class="language-HCL">resource &quot;oci_core_drg_route_distribution_statement&quot; &quot;drg_hub_route_distribution_statement&quot; {
    drg_route_distribution_id = oci_core_drg_route_distribution.drg_hub_route_distribution.id
    action = &quot;ACCEPT&quot;

    match_criteria {
        match_type = &quot;DRG_ATTACHMENT_TYPE&quot;

        attachment_type = &quot;IPSEC_TUNNEL&quot;
    }
    priority = 1
}</code></pre><p>We cannot have overlapping priorities so it is important that each statement has a unique priority number. Here we are specifying DRG_ATTACHMENT_TYPE meaning all the advertised (or static) addresses of that attachment type will be imported, irrespective of how many of those attachments there are (i.e. this will import route rules for ALL IPSec Tunnels). To specify it for only a single tunnel we can use DRG_ATTACHMENT_ID and specify the actual ID, as below;</p><pre><code class="language-HCL">resource &quot;oci_core_drg_route_distribution_statement&quot; &quot;drg_spoke_route_distribution_statement1&quot; {
    drg_route_distribution_id = oci_core_drg_route_distribution.drg_spoke_route_distribution.id
    action = &quot;ACCEPT&quot;

    match_criteria {
        match_type = &quot;DRG_ATTACHMENT_ID&quot;

        attachment_type = &quot;IPSEC_TUNNEL&quot;
        drg_attachment_id = oci_core_drg_attachment_management.multicloud_ipsec_attachment_tunnel_a.id
    }
    priority = 1
}</code></pre><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">The idea behind this setup is that traffic entering the DRG from the Spoke will go in 1 of 2 directions. If it is destined for On-Premise it must go to the Hub VCN so it can be directed through the firewall, if it is destined for Multicloud it must go directly to the IPSec tunnels for Multicloud. Directing On-Premise destined traffic to the Hub VCN can be done from the DRG Spoke VCN Route Table (which tells traffic going to the DRG from the Spoke VCN where its next hop is), as you can add a Static Route in the DRG Route Table to specify the Hub VCN as the next hop for traffic destined to On-Premise. But in the DRG Route Table you cannot add a static route with IPSec Tunnel as the nexthop, so we have to use Import Route Distribution to dynamically populate the DRG Route Table with a nexthop of IPSec Tunnel.</div></div><p>Creating a DRG Attachment in order to attach DRG to VCN is straightforward</p><pre><code class="language-HCL">resource &quot;oci_core_drg_attachment&quot; &quot;hub_drg_attachment&quot; {
  drg_id             = oci_core_drg.drg.id
  display_name       = &quot;Hub-VCN-Attachment&quot;
  drg_route_table_id = oci_core_drg_route_table.drg_hub_rt.id

  network_details {
    id             = oci_core_vcn.hub_vcn.id
    type           = &quot;VCN&quot;
    route_table_id = oci_core_route_table.hub_vcn_ingress_rt.id
  }
}</code></pre><p></p><h3 id="ipsectf">IPSec.tf</h3><p>The creation of CPE &amp; IPSec Connection itself is straight forward</p><pre><code class="language-HCL">resource &quot;oci_core_ipsec&quot; &quot;onprem_ipsec_connection&quot; {
  compartment_id = oci_identity_compartment.demo.id
  cpe_id         = oci_core_cpe.onprem_cpe.id
  drg_id         = oci_core_drg.drg.id
  static_routes  = [oci_core_vcn.onprem_vcn.cidr_block]

  cpe_local_identifier      = oci_core_instance.onprem_libreswan.public_ip
  cpe_local_identifier_type = &quot;IP_ADDRESS&quot;
  display_name = &quot;OnPrem-IPSec&quot;
}</code></pre><p>We&apos;re using Static Routing for the IPSec connection, as opposed to BGP dynamic routing or policy-based routing. Hence we populate static_routes for each IPSec connection, the value simply being our OnPremise IP address range</p><p>In order to update the default tunnel name and specify the tunnel interface IPs, we require to use the <em>oci_core_ipsec_connection_tunnel_management</em> resource</p><pre><code class="language-HCL">resource &quot;oci_core_ipsec_connection_tunnel_management&quot; &quot;onprem_ipsec_tunnel_management_a&quot; {
  ipsec_id  = oci_core_ipsec.onprem_ipsec_connection.id
  tunnel_id = data.oci_core_ipsec_connection_tunnels.onprem_ipsec_tunnels.ip_sec_connection_tunnels[0].id
  depends_on = [data.oci_core_ipsec_connections.onprem_ipsec_connections]

   bgp_session_info {
        customer_interface_ip = &quot;10.10.10.1/30&quot;
        oracle_interface_ip = &quot;10.10.10.2/30&quot;
    }

  display_name  = &quot;OnPrem-IPSec-tunnel-a&quot;
  routing       = &quot;STATIC&quot;
  ike_version   = &quot;V1&quot;
}</code></pre><p>We require a separate resource for each of the 2 tunnels, hence why this resource is called <em>onprem_ipsec_tunnel_management_a</em>, as we have <em>b</em> for the second tunnel</p><p>We also need to attach the DRG Route Table to the IPSec DRG attachment</p><pre><code class="language-HCL">resource &quot;oci_core_drg_attachment_management&quot; &quot;onprem_ipsec_attachment_tunnel_a&quot; {
  attachment_type = &quot;IPSEC_TUNNEL&quot;
  compartment_id = oci_identity_compartment.demo.id
  network_id = data.oci_core_ipsec_connection_tunnels.onprem_ipsec_tunnels.ip_sec_connection_tunnels.0.id
  drg_id = oci_core_drg.drg.id
  display_name = &quot;drg-ipsec-onprem-attachment-tunnel-a&quot;
  drg_route_table_id = oci_core_drg_route_table.drg_onprem_ipsec_rt.id
}</code></pre><p>This needs to be done for each tunnel</p><p></p><h3 id="networkfirewalltf-loggingtf">NetworkFirewall.tf &amp; Logging.tf</h3><p>In order to create a Network Firewall, we first require to create a Network Firewall Policy</p><pre><code class="language-HCL">resource &quot;oci_network_firewall_network_firewall_policy&quot; &quot;network_firewall_policy&quot; {
    compartment_id = oci_identity_compartment.demo.id
    display_name = &quot;Demo-Network-Firewall-Policy&quot;
    security_rules {
        action = &quot;ALLOW&quot;
        condition {}
        name = &quot;Allow-All-Traffic&quot;
    }
}</code></pre><p>Our policy is very straight forward, it has a Security Rule of Allow with no condition; i.e. unconditionally allow all traffic.</p><pre><code class="language-HCL">resource &quot;oci_network_firewall_network_firewall&quot; &quot;network_firewall&quot; {
    compartment_id = oci_identity_compartment.demo.id
    network_firewall_policy_id = oci_network_firewall_network_firewall_policy.network_firewall_policy.id
    subnet_id = oci_core_subnet.hub_sub.id
    display_name = &quot;Demo-Network-Firewall&quot;
}</code></pre><p>We can then create our Network Firewall and pass it our Network Firewall Policy ID</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">The provisioning of Network Firewall is the lengthiest procedure in the terraform apply phase - this can take over 30 minutes</div></div><p>We want to enable Traffic Logging for our Network Firewall so we can view the traffic flowing through our Network Firewall. First we must create a Log Group which will contain our Traffic Log</p><pre><code class="language-HCL">resource &quot;oci_logging_log_group&quot; &quot;log_group&quot; {
    compartment_id = oci_identity_compartment.demo.id
    display_name = &quot;DemoLoggingGroup&quot;
}</code></pre><p>Now we can create and enable our Traffic logging</p><pre><code class="language-HCL">resource &quot;oci_logging_log&quot; &quot;NFW_Log&quot; {
    display_name = &quot;Demo_NetworkFirewall_Log&quot;
    log_group_id = oci_logging_log_group.log_group.id
    log_type = &quot;SERVICE&quot;
    configuration {
        source {
            category = &quot;trafficlog&quot;
            resource = oci_network_firewall_network_firewall.network_firewall.id
            service = &quot;ocinetworkfirewall&quot;
            source_type = &quot;OCISERVICE&quot;
        }
        compartment_id = oci_identity_compartment.demo.id
    }
    is_enabled = true
    retention_duration = 90
}</code></pre><p></p><h3 id="on-premise-multicloud-setup">On-Premise &amp; MultiCloud Setup</h3><p>The On-Premise and MultiCloud sites have the same architecture, which is the one shown below.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2022/10/on-prem-arch.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="453" height="531"></figure><p>The routing is straight forward, the route table for On-Prem CPE/VPN subnet has all traffic going to Internet Gateway (traffic destined for Workload VM will automatically be routed there, no explicit firewall rules are required). The route table for Workload VM however will force all traffic destined to the OCI Site (Spoke VCN) to the CPE/VPN, which in turn will send the traffic through the DRG/Site-to-Site tunnels via the Internet Gateway.</p><p>For the VPN/CPE Subnets route table, we simply use the default route table;</p><pre><code class="language-HCL">resource &quot;oci_core_default_route_table&quot; &quot;onprem_default_route_table&quot; {
    manage_default_resource_id = oci_core_vcn.onprem_vcn.default_route_table_id
    display_name = &quot;Default Route Table&quot;
    route_rules {
        network_entity_id = oci_core_internet_gateway.onprem_igw.id
        destination = &quot;0.0.0.0/0&quot;
        destination_type = &quot;CIDR_BLOCK&quot;
    }
}</code></pre><p>For the Workload VMs Subnet we create a new route table and specify a rule to ensure all traffic destined for the Spoke VCN is sent to the CPE/VPN VM</p><pre><code class="language-HCL">resource &quot;oci_core_route_table&quot; &quot;onprem_wl_rt&quot; {
    compartment_id = oci_identity_compartment.demo.id
    vcn_id = oci_core_vcn.onprem_vcn.id
    display_name = &quot;OnPrem_Priv_RT&quot;
   route_rules {
        network_entity_id = data.oci_core_private_ips.onprem_libreswan_private_ip.private_ips[0].id
        destination = oci_core_vcn.spoke_vcn.cidr_block
        destination_type = &quot;CIDR_BLOCK&quot;
    }
    route_rules {
        network_entity_id = oci_core_internet_gateway.onprem_igw.id
        destination = &quot;0.0.0.0/0&quot;
        destination_type = &quot;CIDR_BLOCK&quot;
    }
}</code></pre><p></p><p>Now we move onto configuring the VPN/CPE/Libreswan via Ansible</p><p>All of the VM and Libreswan configuration is done via Ansible. How do we execute ansible from terraform ? Quite simple, we use the local-exec provisioner to execute the ansible script on the CPE/VPN VM from our terraform host, by giving it the SSH details and the ansible script that needs to be executed. However if we simply do local-exec it will try and execute this before the VM is ready, so we use remote-exec to first ensure we can establish an SSH session</p><pre><code class="language-HCL">resource &quot;null_resource&quot; &quot;onprem-libreswan-config&quot; {
  depends_on = [local_file.ansible-libreswan-vars]
  provisioner &quot;remote-exec&quot; {
    inline = [&quot;echo About to run Ansible on LIBRESWAN and waiting!&quot;]
    connection {
      host        = &quot;${oci_core_instance.onprem_libreswan.public_ip}&quot;
      type        = &quot;ssh&quot;
      user        = &quot;opc&quot;
      private_key = file(var.private_key_path)
    }
  }
  provisioner &quot;local-exec&quot; {
    command = &quot;sleep 30; ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -u opc -i &apos;${oci_core_instance.onprem_libreswan.public_ip},&apos; --private-key &apos;${var.private_key_path}&apos; ./ansible/onprem-libreswan.yml&quot;
  }
}  </code></pre><p>From the above we can gather that the ansible script to be executed is onprem-libreswan.yml and resides in a folder called ansible. Let&apos;s take a look at this script one bit at a time</p><pre><code class="language-yml">- hosts: all
  become: yes
  vars_files:
    - ./vpn_vars/onprem-tunnel.yml
    - ../ansible-libreswan-vars.yml

  tasks:</code></pre><p>We specify the host this is being executed on, any files containing required variables, followed by the series of tasks to be performed on the host</p><pre><code class="language-yml"> - name: install libreswan
    yum:
      name: libreswan
      state: installed </code></pre><p>The first task is straight forward, install Libreswan</p><pre><code class="language-yml">  - name: write the vpn config file
    template: src=onprem-libreswan.j2 dest=/etc/ipsec.d/oci-vpn-tunnels.conf
    become: yes

  - name: write the vpn secrets file
    template: src=onprem_libreswan_secrets.j2 dest=/etc/ipsec.d/oci-vpn-secrets.secrets
    become: true</code></pre><p>Next we copy over the required configuration files for Libreswan, we&apos;ll take a look at how we dynamically create this configuration very soon, but first the remaining ansible tasks</p><pre><code class="language-yml">- name: Enable Ibr_netfilter IPv4
    copy:
      dest: /etc/sysctl.conf
      content: |
        net.ipv4.ip_forward = 1
        net.ipv4.conf.default.rp_filter = 0
        net.ipv4.conf.all.rp_filter = 0
        net.ipv4.conf.all.send_redirects = 0
        net.ipv4.conf.default.send_redirects = 0
        net.ipv4.icmp_ignore_bogus_error_responses = 1
        net.ipv4.conf.default.log_martians = 0
        net.ipv4.conf.all.log_martians = 0
        net.ipv4.conf.default.accept_source_route = 0
        net.ipv6.conf.default.accept_source_route = 0
        net.ipv4.conf.all.accept_redirects = 0
        net.ipv6.conf.all.accept_redirects = 0
        net.ipv4.conf.default.accept_redirects = 0
        net.ipv6.conf.default.accept_redirects = 0
    
  - name: Apply Persistent IPv4 Forwarding
    shell: sudo sysctl -p

  - name: Disable Selinux
    shell: sudo setenforce 0

  - name: Disable Firewalld
    shell: sudo systemctl stop firewalld

  - name: ensure ipsec is running
    service: name=ipsec state=started
    become: yes

  - name: Apply Persistent IPSEC connection
    shell: systemctl enable ipsec.service
    become: yes</code></pre><p>Next we enable IP forwarding, as well as modifying some kerner parameters.</p><p>We disable Selinux, firewalld and start libreswan/ipsec</p><pre><code class="language-yml">  - name: Configure Hub Static Route
    shell: sudo ip route add {{ oci_hub_cidr }} nexthop dev vti1 nexthop dev vti2

  - name: Configure Spoke Static Route
    shell: sudo ip route add {{ oci_spoke_cidr }} nexthop dev vti1 nexthop dev vti2</code></pre><p>We then add static routing such that all traffic destined to OCI (Hub VCN &amp; Spoke VCN) passes through the Virtual Tunnel Interface&apos;s</p><p></p><p>Libreswan has 2 main files, 1 is for the IPSec configuration and the 2nd holds the Shared Secret for the Site-to-Site VPN Tunnels. The ansible yml code we saw previously is copying these 2 files from your terraform host server to the CPE/VPN VMs. Let&apos;s take a look at the 2 files;</p><pre><code class="language-j2">conn {{ onprem_conn_name_tunnel1 }}
     authby=secret
     pfs=yes
     left={{ onprem_cpe_local_ip }}
     leftid={{ onprem_cpe_public_ip }}
     leftsubnet=0.0.0.0/0
     leftnexthop=%defaultroute
     right={{ onprem_oci_tunnel1_headend }}
     rightid={{ onprem_oci_tunnel1_headend }}    
     rightsubnet=0.0.0.0/0
     mark=5/0xffffffff # Needs to be unique across all tunnels
     vti-interface=vti1
     vti-routing=no
     leftvti={{ onprem_tunnel1_leftvti }}
     ikev2=no # To use IKEv2, change to ikev2=insist
     ike=aes_cbc256-sha2_384;modp1536
     phase2alg=aes_gcm256;modp1536
     encapsulation=yes
     ikelifetime=28800s
     salifetime=3600s
     auto=start
conn {{ onprem_conn_name_tunnel2 }}
     authby=secret
     pfs=yes
     left={{ onprem_cpe_local_ip }}
     leftid={{ onprem_cpe_public_ip }}
     leftsubnet=0.0.0.0/0
     leftnexthop=%defaultroute
     right={{ onprem_oci_tunnel2_headend }}
     rightid={{ onprem_oci_tunnel2_headend }}    
     rightsubnet=0.0.0.0/0
     mark=6/0xffffffff # Needs to be unique across all tunnels
     vti-interface=vti2
     vti-routing=no
     leftvti={{ onprem_tunnel2_leftvti }}
     ikev2=no # To use IKEv2, change to ikev2=insist
     ike=aes_cbc256-sha2_384;modp1536
     phase2alg=aes_gcm256;modp1536
     encapsulation=yes
     ikelifetime=28800s
     salifetime=3600s
     auto=start</code></pre><p>This is the basic configuration file for Libreswan. Here we are setting up 2 IPSec tunnels, connecting to 2 Public IPs which connect to a single DRG.</p><p>The source secrets file which holds the Shared Secret for each tunnel looks like this</p><pre><code class="language-j2">{{ onprem_cpe_public_ip }} {{ onprem_oci_tunnel1_headend }} : PSK &quot;{{ onprem_shared_secret_psk1 }}&quot;
{{ onprem_cpe_public_ip }} {{ onprem_oci_tunnel2_headend }} : PSK &quot;{{ onprem_shared_secret_psk2 }}&quot;</code></pre><p>As you can see there are variables for the actual values; this allows us to dynamically create the configuration based on our specific implementation as whenever you run the terraform and provision new resources we&apos;ll have a new set of Public IPs, Private IPs, Shared Secret Keys, IPSec Headend IPs etc. So how do we define these variables ?</p><h3 id="ansible-varstf">ansible-vars.tf</h3><p>We dynamically create a new file containing our variable names and their values. The values are extracted from either the terraform resources we create or the data sources that we define</p><pre><code class="language-HCL">resource &quot;local_file&quot; &quot;ansible-libreswan-vars&quot; {
  content = &lt;&lt;-DOC
    # Ansible vars_file containing variable values from Terraform.
    # Generated by Terraform mgmt configuration.
    onprem_cpe_local_ip: ${oci_core_instance.onprem_libreswan.private_ip}
    onprem_cpe_public_ip: ${oci_core_instance.onprem_libreswan.public_ip}
    onprem_oci_tunnel1_headend: ${data.oci_core_ipsec_connection_tunnels.onprem_ipsec_tunnels.ip_sec_connection_tunnels[0].vpn_ip}
    onprem_oci_tunnel2_headend: ${data.oci_core_ipsec_connection_tunnels.onprem_ipsec_tunnels.ip_sec_connection_tunnels[1].vpn_ip}
    onprem_shared_secret_psk1: ${data.oci_core_ipsec_config.onprem_ipsec_config.tunnels[0].shared_secret}
    onprem_shared_secret_psk2: ${data.oci_core_ipsec_config.onprem_ipsec_config.tunnels[1].shared_secret}
    multicloud_cpe_local_ip: ${oci_core_instance.multicloud_libreswan.private_ip}
    multicloud_cpe_public_ip: ${oci_core_instance.multicloud_libreswan.public_ip}
    multicloud_oci_tunnel1_headend: ${data.oci_core_ipsec_connection_tunnels.multicloud_ipsec_tunnels.ip_sec_connection_tunnels[0].vpn_ip}
    multicloud_oci_tunnel2_headend: ${data.oci_core_ipsec_connection_tunnels.multicloud_ipsec_tunnels.ip_sec_connection_tunnels[1].vpn_ip}
    multicloud_shared_secret_psk1: ${data.oci_core_ipsec_config.multicloud_ipsec_config.tunnels[0].shared_secret}
    multicloud_shared_secret_psk2: ${data.oci_core_ipsec_config.multicloud_ipsec_config.tunnels[1].shared_secret}
    oci_hub_cidr: ${oci_core_vcn.hub_vcn.cidr_block}
    oci_spoke_cidr: ${oci_core_vcn.spoke_vcn.cidr_block}
    DOC
  filename = &quot;./ansible-libreswan-vars.yml&quot;</code></pre><p>We also have another file which contains the tunnels inside IPs a long with some other required information (FYI, we could add these variables to our script above and then have a single file)</p><pre><code class="language-yml">---
#Name for the VPN Connetion
onprem_conn_name_tunnel1: oci-tunnel1
onprem_conn_name_tunnel2: oci-tunnel2

#Connection settings
onprem_tunnel1_cidr: &quot;10.10.10.1/30&quot;
onprem_tunnel2_cidr: &quot;10.10.10.5/30&quot;
onprem_left: &quot;{{ onprem_cpe_local_ip }}&quot;
onprem_tunnel1_right: &quot;{{ onprem_oci_tunnel1_headend }}&quot;
onprem_tunnel2_right: &quot;{{ onprem_oci_tunnel2_headend }}&quot;
onprem_vti1: &quot;vti1&quot;
onprem_vti2: &quot;vti2&quot;
onprem_oci_vcn_cidr: &quot;{{ onprem_oci_vcn_cidr }}&quot;
onprem_tunnel1_leftvti: &quot;10.10.10.1/30&quot;
onprem_tunnel2_leftvti: &quot;10.10.10.5/30&quot;
#PSK to be used. 
onprem_vpn_psk1: &quot;{{ onprem_shared_secret_psk1 }}&quot;
onprem_vpn_psk2: &quot;{{ onprem_shared_secret_psk2 }}&quot;</code></pre><p></p><h2 id="traffic-flow">Traffic Flow</h2><p>With all of that covered, let&apos;s now take a look at the actual traffic flow by looking at each hope, the route table involved and the route rule for our traffic.</p><p>We&apos;ll look at the perspective of traffic coming from our Workload VM and going to our Site-to-Site VPNs.</p><h3 id="to-on-premise-via-network-firewall">To On-Premise (via Network Firewall)</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2023/01/image-3.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="1216" height="678" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-3.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-3.png 1000w, https://highoncloud.co.uk/content/images/2023/01/image-3.png 1216w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Let&apos;s first take a look at the traffic from Workload VM to On-Premise Site</span></figcaption></figure><p>On-Premise Network has a CIDR of 172.0.0.0/16</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-2.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="1776" height="800" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-2.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-2.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2023/01/image-2.png 1600w, https://highoncloud.co.uk/content/images/2023/01/image-2.png 1776w" sizes="(min-width: 720px) 720px"></figure><p>When trying to access On-Premise Site from the Workload VM, the route table in play first is the VCN Route Table associated with the subnet. In here we specify what traffic we want to go where; in this case we want the On-Premise CIDR (Destination) to go to the Demo-DRG, which is the next hop</p><p></p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-5.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="2000" height="914" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-5.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-5.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2023/01/image-5.png 1600w, https://highoncloud.co.uk/content/images/size/w2400/2023/01/image-5.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Now that the traffic is being passed to the DRG, we need to instruct the DRG where to pass the traffic to. The Route Table in play here is the <em>DRG Route Table</em> associated with this specific <em>DRG Attachment.</em></p><p>The next hop in this route tables rule is the Hub VCN which is accessed via a <em>DRG Attachment</em>, which is why the Target Type is <em>VCN</em> and the Target is the Hub VCN DRG Attachment</p><p></p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-6.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="2000" height="990" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-6.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-6.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2023/01/image-6.png 1600w, https://highoncloud.co.uk/content/images/size/w2400/2023/01/image-6.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>The traffic is now being passed to the Hub VCN via the <em>DRG Attachment</em>. The Route Table in play is the VCN Route Table associated with the <em>DRG Attachment</em> connecting the Hub VCN and DRG. The Route Rule for our traffic is to the Private IP of the Network Firewall.</p><p></p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-7.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="2000" height="989" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-7.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-7.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2023/01/image-7.png 1600w, https://highoncloud.co.uk/content/images/size/w2400/2023/01/image-7.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>The traffic has passed through the Network Firewall, which has hopefully allowed the traffic through, so the Route Table in play now is the VCN Route Table associated with the subnet (in our case, the VCNs default route table). The Route Rule for our traffic sends the traffic to the DRG.</p><p></p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-8.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="2000" height="989" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-8.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-8.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2023/01/image-8.png 1600w, https://highoncloud.co.uk/content/images/size/w2400/2023/01/image-8.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Now that the traffic is being sent to the DRG from the VCN, the Route Table in play is now the DRG Route Table associated with the DRG Attachment which connects the Hub VCN to the DRG.</p><p>The Route Rule targeting our traffic sends the traffic to the On-Premise IPSec, which will reach the On-Premise CPE. This Route Rule cannot be created manually (statically), instead it is done dynamically by using the Import Route Distribution associated with the DRG Route Table.</p><p>This has been explained in the <a href="#dynamic-route-import-distribution">Dynamic Route Import Distribution</a> section</p>
<h3 id="to-multicloud-bypassing-network-firewall">To Multicloud (bypassing Network Firewall)</h3><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-13.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="1033" height="583" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-13.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-13.png 1000w, https://highoncloud.co.uk/content/images/2023/01/image-13.png 1033w" sizes="(min-width: 720px) 720px"></figure><p>The traffic to Multicloud Site follows a much simpler path!</p><p></p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-10.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="1539" height="692" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-10.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-10.png 1000w, https://highoncloud.co.uk/content/images/2023/01/image-10.png 1539w" sizes="(min-width: 720px) 720px"></figure><p>Traffic from our Workload VM in the Spoke VCN, to the Multicloud site, is given its first hop by the VCN Route Table associated with our subnet (in our case, the VCN default route table). The route rule tells all traffic destined for Multicloud to be sent to the DRG.</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2023/01/image-11.png" class="kg-image" alt="Advanced Networking - Using DRG to route certain traffic through centralized Network Firewall (and other traffic direct to Spoke) [Terraform]" loading="lazy" width="1538" height="708" srcset="https://highoncloud.co.uk/content/images/size/w600/2023/01/image-11.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2023/01/image-11.png 1000w, https://highoncloud.co.uk/content/images/2023/01/image-11.png 1538w" sizes="(min-width: 720px) 720px"></figure><p>Once the traffic hits the DRG, the Route Table in play is the DRG Route Table associated with the DRG Attachment which connects the Spoke VCN to the DRG.</p><p>The route rule in this table tells the traffic that the next hop is the IPSec tunnel. As mentioned previously, this particular rule for IPSec is dynamically populated using the Import Route Distribution.</p>]]></content:encoded></item><item><title><![CDATA[[Updated] Creating a Free Blog on OCI]]></title><description><![CDATA[An updated step-by-step guide to create a secure blog on OCI hosted by 4 OCPUs & 24GB RAM, whilst following best practice!]]></description><link>https://highoncloud.co.uk/updated-creating-a-free-blog-on-oci/</link><guid isPermaLink="false">6595bd09aec486e263abf524</guid><dc:creator><![CDATA[Shahvaiz Janjua]]></dc:creator><pubDate>Thu, 04 Jan 2024 00:11:42 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2022/03/maincover-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2022/03/maincover-1.png" alt="[Updated] Creating a Free Blog on OCI"><p></p><p>In this updated article we&apos;ll walkthrough the steps to create a free blog hosted on Oracle Cloud Infrastructure, utilising Oracle&apos;s very generous Ampere A1 instance which gives you 4 OCPUs (8 Threads) &amp; 24GB RAM for FREE. We&apos;ll be also be using the Always Free Network Load Balancer &amp; Bastion Service. We&apos;ll first have a very short introduction into the various components involved in this setup, then look at the process and start getting to work! Feel free to skip the theory and jump to whatever section you&apos;re interested in.</p><div class="kg-card kg-header-card kg-width-full kg-size-small kg-style-dark" data-kg-background-image style><h2 class="kg-header-card-header" id="components"><span style="white-space: pre-wrap;">Components:</span></h2></div><h2 id="components-involved">Components Involved:</h2><ul>
<li>OCI - Network Load Balancer</li>
<li>OCI - A1 Ampere Compute Instance</li>
<li>OCI - Bastion Service</li>
<li>OCI - Other Core Networking (VCN, Subnet, Internet &amp; NAT Gateway etc)</li>
<li>Ghost</li>
<li>Lets Encrypt</li>
<li>NoIP</li>
</ul>
<h2 id="explanation-of-some-components-involved">Explanation of some Components Involved:</h2><h3 id="oracle-cloud-infrastructure">Oracle Cloud Infrastructure</h3><p>Oracle Cloud Free Tier offers <a href="https://www.oracle.com/cloud/free/?ref=highoncloud.co.uk">Always Free</a> services, which includes a Compute VM which we&apos;ll use to host our free website</p><h3 id="ocinetwork-load-balancer">OCI - Network Load Balancer</h3><p>Network Load Balancer provides the benefits of flow high availability, source and destination IP addresses, and port preservation. It is designed to handle volatile traffic patterns and millions of flows, offering high throughput while maintaining ultra low latency. Network load balancers have a default 1 million concurrent connection limit. Network Load Balancer is the ideal load balancing solution for latency sensitive workloads.</p><h3 id="ghost">Ghost</h3><p>Ghost is a free, opensource and simple web application from which you can create and manage your blog and contents. It has the combined advantage of automatically configuring nginx as a reverse proxy and for HTTPS, as well as obtaining the SSL Certificate and making a crontab entry to automatically renew these certificates. </p><h3 id="lets-encrypt">Let&apos;s Encrypt</h3><p>Let&apos;s Encrypt is a free, opensource and automated Certificate Authority. Let&apos;s Encrypt offer FREE SSL/TLS Certificates! They are only valid for 90 days, but can simply be renewed.</p><h3 id="noip">NoIP</h3><p>NoIP offer free hostnames under select few domains, they also provide you with complete control over the DNS records for that hostname, so we can the website address directly to our Free OCI VM</p><div class="kg-card kg-header-card kg-width-full kg-size-small kg-style-dark" data-kg-background-image style><h2 class="kg-header-card-header" id="process-amp-architecture"><span style="white-space: pre-wrap;">Process &amp; Architecture</span></h2></div><h2 id="process">Process</h2><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2022/03/Process.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="847" height="73" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/03/Process.png 600w, https://highoncloud.co.uk/content/images/2022/03/Process.png 847w" sizes="(min-width: 720px) 720px"></figure><p>The process itself is quite straight forward. First we&apos;ll setup the underlying infrastructure in OCI to support the provisioning of our Free VM. We&apos;ll then Create and Configure the domain and DNS and point it to our OCI VM (This must be done before configuring Ghost in order to obtain and configure SSL Certs). We&apos;ll then finish up by installing Ghost!</p><h2 id="architecture">Architecture</h2><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2022/03/mainarch.drawio.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="723" height="541" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/03/mainarch.drawio.png 600w, https://highoncloud.co.uk/content/images/2022/03/mainarch.drawio.png 723w" sizes="(min-width: 720px) 720px"></figure><p>The architecture is quite straight forward. We have a Public Subnet and a Private Subnet. The Network Load Balancer, which resides in the Public Subnet and has a Public IP, will receive HTTPS traffic from Public Internet and will direct it to the Private Compute instance, inside the Private Subnet, hosting our blogging software. </p><p>The Compute Instance in a Private Subnet is not directly accessible via Public Internet, for security reasons. The Compute Instance can however talk out to the internet via NAT Gateway in order to obtain required software and updates. We&apos;ll utilise the free Bastion Service to access the private Compute Instance to perform our admin tasks. </p><div class="kg-card kg-header-card kg-width-full kg-size-small kg-style-dark" data-kg-background-image style><h2 class="kg-header-card-header" id="tutorial"><span style="white-space: pre-wrap;">Tutorial</span></h2></div><p>If you don&apos;t already have an OCI tenancy then go and grab your <a href="https://www.oracle.com/cloud/free/?ref=highoncloud.co.uk">Always Free</a> account...</p><h3 id="create-networking">Create Networking</h3><p></p>
<!--kg-card-begin: html-->
<section>

<figcaption style="text-align: center" ;></figcaption>
    
<p>To speed things a long we&apos;ll use the Network Wizard; which we can select from the Launch resources window in Get Started tab in the Home Screen
  </p>
  <br>
<figure style="float: right; width: 60%; margin-left: 15px; margin-right: 0; margin-top: 10px;">
    <img src="https://highoncloud.co.uk/content/images/2022/08/Screenshot-2022-03-17-at-15.02.52.png" alt="[Updated] Creating a Free Blog on OCI">
    </figure>
  <p>
    <ul>
        <li>Ensure &quot;Create VCN with Internet Connectivity&quot; is selected</li>
        <li>Click &quot;Start VCN Wizard&quot;</li>
    </ul>
</p>
</section>
<!--kg-card-end: html-->
<p></p><p></p><p></p><p>Fill in all of the information pertaining to our VCN setup</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2022/08/Screenshot-2022-03-17-at-15.03.37.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1617" height="872" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/08/Screenshot-2022-03-17-at-15.03.37.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2022/08/Screenshot-2022-03-17-at-15.03.37.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2022/08/Screenshot-2022-03-17-at-15.03.37.png 1600w, https://highoncloud.co.uk/content/images/2022/08/Screenshot-2022-03-17-at-15.03.37.png 1617w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Enter the VCN Name and Populate the VCN CIDR Block and Subnet CIDRs. Click &quot;Next&quot;</span></figcaption></figure><p></p>
<!--kg-card-begin: html-->
<section>
<figure style="float: right; width: 40%; margin-left: 15px; margin-right: 0;">
<img src="https://highoncloud.co.uk/content/images/2022/08/image-1.png" alt="[Updated] Creating a Free Blog on OCI">
<figcaption style="text-align: center" ;></figcaption>
    </figure>
<p>
Review the Networking Information, Click &quot;Create&quot;
</p>
</section>
<!--kg-card-end: html-->
<p></p><p></p><p></p><p></p>
<!--kg-card-begin: html-->
<section>
<figure style="float: right; width: 40%; margin-left: 15px; margin-right: 0;">
<img src="https://highoncloud.co.uk/content/images/2022/08/image-2.png" alt="[Updated] Creating a Free Blog on OCI">
<figcaption style="text-align: center" ;></figcaption>
    </figure>
<p>Networking Resources will be provisioned and can be viewed once Provisioning is completed
</p>
</section>
<!--kg-card-end: html-->
<p></p><p></p><p></p><h2 id="update-security-rules">Update Security Rules</h2><p></p><p>When viewing the VCN, navigate through the following;</p><ul>
<li>Click &quot;Security Lists&quot; under &quot;Resources&quot;</li>
<li>Click on the private subnet security list</li>
<li>Select first line (with TCP Protocol &amp; Destination Port 22)</li>
<li>Click Edit</li>
</ul>
<figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/01/image-11.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1127" height="482" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-11.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/01/image-11.png 1000w, https://highoncloud.co.uk/content/images/2024/01/image-11.png 1127w" sizes="(min-width: 720px) 720px"></figure><ul><li>Append Destination Port Range with &quot;,443,80&quot;</li><li>Click Save changes</li></ul><p></p><h2 id="create-compute-instance">Create Compute Instance</h2><p></p><p>Select Create VM from the Launch resources window in Get Started tab in the Home Screen, then fill out the VM configuration information;</p><ul><li>Change the image to Canonical Ubuntu 20.04</li><li>Change the shape VM.Standard.A1.Flex with 4 OCPUs &amp; 24 GB memory</li><li>Ensure you select the private subnet for the Subnet of the Primary VNIC</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2022/08/image-12.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1123" height="1094" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/08/image-12.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2022/08/image-12.png 1000w, https://highoncloud.co.uk/content/images/2022/08/image-12.png 1123w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Download the SSH keys or upload your own, then click Create</span></figcaption></figure><p></p><p></p><h2 id="create-network-load-balancer">Create Network Load Balancer</h2><p></p><p>Select &quot;Set up a load balancer&quot; from the Launch resources window in Get Started tab in the Home Screen</p><p>Click &quot;Create network load balancer&quot; populate the require information</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2024/01/image-3.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1224" height="856" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-3.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/01/image-3.png 1000w, https://highoncloud.co.uk/content/images/2024/01/image-3.png 1224w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Ensure you select the public subnet under &quot;Choose networking&quot;. Click &quot;Next&quot;</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2024/01/image-4.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1251" height="443" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-4.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/01/image-4.png 1000w, https://highoncloud.co.uk/content/images/2024/01/image-4.png 1251w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Select &quot;TCP&quot;, &quot;Use any port&quot; will be selected by default. Click &quot;Next&quot;</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2024/01/image-7.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1216" height="1140" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-7.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/01/image-7.png 1000w, https://highoncloud.co.uk/content/images/2024/01/image-7.png 1216w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Add your Compute as a backend. Under &quot;Specify health check policy&quot; change the Protocol to &quot;TCP&quot; and the Port to &quot;22&quot;. Click Next, review and Create the Network Loadbalancer</span></figcaption></figure><p></p><h2 id="create-bastion">Create Bastion</h2><p></p><p>Click on the Hamburger symbol on the top left of the console.<br>Under &quot;Identity &amp; Security&quot;, click &quot;Bastion&quot; and then &quot;Create Bastion&quot;</p><p>Provide a name and select the VCN and private Subnet, allow 0.0.0.0/0 or your computers public IP/32 in the CIDR allowlist</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2022/08/image-16.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1118" height="573" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/08/image-16.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2022/08/image-16.png 1000w, https://highoncloud.co.uk/content/images/2022/08/image-16.png 1118w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Once the Bastion service is created, click on the service and click &quot;Create session&quot;</p><p>provide a username, select the compute instance we created and upload an SSH key</p><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2024/01/image-9.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1134" height="817" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-9.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/01/image-9.png 1000w, https://highoncloud.co.uk/content/images/2024/01/image-9.png 1134w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Once the session is created, copy the SSH command</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2024/01/image-10.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1447" height="411" srcset="https://highoncloud.co.uk/content/images/size/w600/2024/01/image-10.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2024/01/image-10.png 1000w, https://highoncloud.co.uk/content/images/2024/01/image-10.png 1447w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Click on the 3 dots on the right side of the Session and click &quot;Copy SSH command&quot;</span></figcaption></figure><p>Save this command</p><pre><code class="language-bash">ssh -i &lt;privateKey&gt; -N -L &lt;localPort&gt;:10.0.1.222:22 -p 22 ocid1.bastionsession.oc1.eu-frankfurt-1.amaaaaaaztrrnmqaba365sr2ckqrmedj3gxuazdlprxexfxvtacjhej2dxda@host.bastion.eu-frankfurt-1.oci.oraclecloud.com</code></pre><p></p><h2 id="create-configure-domain">Create &amp; Configure Domain</h2><p></p>
<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-05.27.46-1.png" style="float:right; margin-left: 20px; width: 300px; height:100px" alt="[Updated] Creating a Free Blog on OCI">
    Head over to https://www.noip.com/ and grab your free domain
</div>
<!--kg-card-end: html-->
<p></p><p>Create an A record for your domain</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://highoncloud.co.uk/content/images/2022/08/image-29.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="968" height="764" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/08/image-29.png 600w, https://highoncloud.co.uk/content/images/2022/08/image-29.png 968w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Ensure your IPv4 Address is the Network Loadbalancer Public IP</span></figcaption></figure><p></p><h2 id="install-ghost">Install Ghost</h2><p></p><h3 id="connect-to-vm">Connect to VM</h3><p>Let&apos;s look at the SSH command we copied</p><pre><code>ssh -i &lt;privateKey&gt; -N -L &lt;localPort&gt;:10.0.1.222:22 -p 22 ocid1.bastionsession.oc1.eu-frankfurt-1.amaaaaaaztrrnmqaba365sr2ckqrmedj3gxuazdlprxexfxvtacjhej2dxda@host.bastion.eu-frankfurt-1.oci.oraclecloud.com</code></pre><p>All we need to do to setup an SSH tunnel is give it the private key location and a local port. Execute this command</p><pre><code>ssh -i sjanjua_rsa.ppk -N -L 9500:10.0.1.222:22 -p 22 ocid1.bastionsession.oc1.eu-frankfurt-1.amaaaaaaztrrnmqaba365sr2ckqrmedj3gxuazdlprxexfxvtacjhej2dxda@host.bastion.eu-frankfurt-1.oci.oraclecloud.com</code></pre><p>You can then SSH into them</p><pre><code>ssh ubuntu@localhost -p 9500</code></pre><p></p><h3 id="ghost-setup">Ghost Setup</h3><p>Now let&apos;s start with the Ghost setup installation.</p><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo apt upgrade
sudo apt update</code></pre><figcaption><p><span style="white-space: pre-wrap;">Perform an update of the packages</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo iptables -I INPUT 5 -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT 5 -p tcp --dport 443 -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4</code></pre><figcaption><p><span style="white-space: pre-wrap;">Open up the OS Firewall for ports 80 and 443</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo adduser ghostusr
sudo usermod -aG sudo ghostusr
sudo su - ghostusr</code></pre><figcaption><p><span style="white-space: pre-wrap;">Create a user for Ghost install, allow the user access to sudo</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo apt install cron
sudo apt-get install nginx
sudo apt-get install mysql-server
sudo apt-get install build-essential</code></pre><figcaption><p><span style="white-space: pre-wrap;">Install cron, nginx and mysql-server. We&apos;ll also install build-essential to avoid an error during ghost install</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo mysql</code></pre><figcaption><p><span style="white-space: pre-wrap;">Login to mysql</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-SQL">alter user &apos;root&apos;@&apos;localhost&apos; identified with mysql_native_password by &apos;oracle99&apos;;
quit;</code></pre><figcaption><p><span style="white-space: pre-wrap;">Set the root user password</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash
sudo apt-get install -y nodejs</code></pre><figcaption><p><span style="white-space: pre-wrap;">Install nodejs</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo npm install ghost-cli@latest -g</code></pre><figcaption><p><span style="white-space: pre-wrap;">Install Ghost CLI</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo mkdir -p /var/www/ghost
sudo chown ghostusr:ghostusr /var/www/ghost
sudo chmod 775 /var/www/ghost
cd /var/www/ghost</code></pre><figcaption><p><span style="white-space: pre-wrap;">Create a directory to host the Ghost Instance</span></p></figcaption></figure><blockquote>Run the Ghost installation. You&apos;ll need to provide the URL, mysql details and an email address.</blockquote><p>Answer Yes to all responses</p><pre><code class="language-Bash">ghost install 
&#x2714; Checking system Node.js version - found v14.20.0
&#x2714; Checking logged in user
&#x2714; Checking current folder permissions
&#x2714; Checking system compatibility
&#x2714; Checking for a MySQL installation
&#x2714; Checking memory availability
&#x2714; Checking free space
&#x2714; Checking for latest Ghost version
&#x2714; Setting up install directory
&#x2714; Downloading and installing Ghost v5.12.0
&#x2714; Finishing install process
? Enter your blog URL: https://highoncloud.ddns.net
? Enter your MySQL hostname: localhost
? Enter your MySQL username: root
? Enter your MySQL password: [hidden]
? Enter your Ghost database name: ghostdb
&#x2714; Configuring Ghost
&#x2714; Setting up instance
+ sudo chown -R ghost:ghost /var/www/ghost/content
&#x2714; Setting up &quot;ghost&quot; system user
? Do you wish to set up &quot;ghost&quot; mysql user? Yes
&#x2714; Setting up &quot;ghost&quot; mysql user
? Do you wish to set up Nginx? Yes
+ sudo mv /tmp/highoncloud-ddns-net/highoncloud.ddns.net.conf /etc/nginx/sites-available/highoncloud.ddns.net.conf
+ sudo ln -sf /etc/nginx/sites-available/highoncloud.ddns.net.conf /etc/nginx/sites-enabled/highoncloud.ddns.net.conf
+ sudo nginx -s reload
&#x2714; Setting up Nginx
? Do you wish to set up SSL? Yes
? Enter your email (For SSL Certificate) sjspm1@gmail.com
+ sudo /etc/letsencrypt/acme.sh --upgrade --home /etc/letsencrypt
+ sudo /etc/letsencrypt/acme.sh --issue --home /etc/letsencrypt --server letsencrypt --domain highoncloud.ddns.net --webroot /var/www/ghost/system/nginx-root --reloadcmd &quot;nginx -s reload&quot; --accountemail sjspm1@gmail.com
+ sudo openssl dhparam -dsaparam -out /etc/nginx/snippets/dhparam.pem 2048
+ sudo mv /tmp/ssl-params.conf /etc/nginx/snippets/ssl-params.conf
+ sudo mv /tmp/highoncloud-ddns-net/highoncloud.ddns.net-ssl.conf /etc/nginx/sites-available/highoncloud.ddns.net-ssl.conf
+ sudo ln -sf /etc/nginx/sites-available/highoncloud.ddns.net-ssl.conf /etc/nginx/sites-enabled/highoncloud.ddns.net-ssl.conf
+ sudo nginx -s reload
&#x2714; Setting up SSL
? Do you wish to set up Systemd? Yes
+ sudo mv /tmp/highoncloud-ddns-net/ghost_highoncloud-ddns-net.service /lib/systemd/system/ghost_highoncloud-ddns-net.service
+ sudo systemctl daemon-reload
&#x2714; Setting up Systemd
+ sudo systemctl is-active ghost_highoncloud-ddns-net
? Do you want to start Ghost? Yes
+ sudo systemctl start ghost_highoncloud-ddns-net
+ sudo systemctl is-enabled ghost_highoncloud-ddns-net
+ sudo systemctl enable ghost_highoncloud-ddns-net --quiet
&#x2714; Starting Ghost

Ghost uses direct mail by default. To set up an alternative email method read our docs at https://ghost.org/docs/config/#mail

------------------------------------------------------------------------------

Ghost was installed successfully! To complete setup of your publication, visit:

    https://highoncloud.ddns.net/ghost/</code></pre><p></p><h2 id="checkout-your-new-site">Checkout your new site!</h2><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2022/08/image-30.png" class="kg-image" alt="[Updated] Creating a Free Blog on OCI" loading="lazy" width="1518" height="1134" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/08/image-30.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2022/08/image-30.png 1000w, https://highoncloud.co.uk/content/images/2022/08/image-30.png 1518w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Creating a Free (HTTPS) Blog on Oracle Cloud]]></title><description><![CDATA[<p>In this article we&apos;ll walkthrough the steps to create a free website or blog hosted on Oracle Cloud Infrastructure (FYI, at the time of writing this article this site is hosted on OCI using these very steps). We&apos;ll first have a very short introduction into the</p>]]></description><link>https://highoncloud.co.uk/free-blog-on-oci/</link><guid isPermaLink="false">62328610dfae694b73185b8a</guid><dc:creator><![CDATA[Shahvaiz Janjua]]></dc:creator><pubDate>Sat, 30 Jan 2021 18:26:03 GMT</pubDate><media:content url="https://highoncloud.co.uk/content/images/2022/03/free-blog-img2.png" medium="image"/><content:encoded><![CDATA[<img src="https://highoncloud.co.uk/content/images/2022/03/free-blog-img2.png" alt="Creating a Free (HTTPS) Blog on Oracle Cloud"><p>In this article we&apos;ll walkthrough the steps to create a free website or blog hosted on Oracle Cloud Infrastructure (FYI, at the time of writing this article this site is hosted on OCI using these very steps). We&apos;ll first have a very short introduction into the various components involved in this setup, then look at the process and start getting to work! Feel free to skip the theory and jump to whatever section you&apos;re interested in.</p><h2 id="components">Components:</h2><h3 id="oracle-cloud-infrastructure">Oracle Cloud Infrastructure</h3><p>Oracle Cloud Free Tier offers <a href="https://www.oracle.com/cloud/free/?ref=highoncloud.co.uk">Always Free</a> services, which includes a Compute VM which we&apos;ll use to host our free website</p><h3 id="ghost">Ghost</h3><p>Ghost is a free, opensource and simple web application from which you can create and manage your blog and contents. It has the combined advantage of automatically configuring nginx as a reverse proxy and for HTTPS, as well as obtaining the SSL Certificate and making a crontab entry to automatically renew these certificates. </p><h3 id="lets-encrypt">Let&apos;s Encrypt</h3><p>Let&apos;s Encrypt is a free, opensource and automated Certificate Authority. Let&apos;s Encrypt offer FREE SSL/TLS Certificates! They are only valid for 90 days, but can simply be renewed.</p><h3 id="noip">NoIP</h3><p>NoIP offer free hostnames under select few domains, they also provide you with complete control over the DNS records for that hostname, so we can the website address directly to our Free OCI VM</p><h2 id="process">Process</h2><figure class="kg-card kg-image-card"><img src="https://highoncloud.co.uk/content/images/2022/03/free-blog-on-oci-flowchart.png" class="kg-image" alt="Creating a Free (HTTPS) Blog on Oracle Cloud" loading="lazy" width="2000" height="536" srcset="https://highoncloud.co.uk/content/images/size/w600/2022/03/free-blog-on-oci-flowchart.png 600w, https://highoncloud.co.uk/content/images/size/w1000/2022/03/free-blog-on-oci-flowchart.png 1000w, https://highoncloud.co.uk/content/images/size/w1600/2022/03/free-blog-on-oci-flowchart.png 1600w, https://highoncloud.co.uk/content/images/2022/03/free-blog-on-oci-flowchart.png 2379w" sizes="(min-width: 720px) 720px"></figure><p>The process itself is quite straight forward. First we&apos;ll setup the underlying infrastructure in OCI to support the provisioning of our Free VM. We&apos;ll then Create and Configure the domain and DNS and point it to our OCI VM (This must be done before configuring Ghost in order to obtain and configure SSL Certs). We&apos;ll then finish up by installing Ghost! Here we go!</p><h2 id="tutorial">Tutorial</h2><p>If you don&apos;t already have an OCI tenancy then go and grab your <a href="https://www.oracle.com/cloud/free/?ref=highoncloud.co.uk">Always Free</a> account...</p><h3 id="create-an-always-free-instance">Create an Always Free Instance</h3>
<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-04.35.14-1.png" style="float:right; margin-left: 20px; width: 300px; height:300px" alt="Creating a Free (HTTPS) Blog on Oracle Cloud">
    Once you&apos;ve logged in, select &quot;Create a VM&quot;<br>This process will create the underlying network as well as creating the VM
</div>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div>
    <img alt="Creating a Free (HTTPS) Blog on Oracle Cloud" src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-14.26.11.png" style="float:right; margin-left: 20px; width: 450px; height:225px">
    Select the &quot;Always Free Eligable&quot; Shape<br>
    Select &quot;Canonical Ubuntu 20.04 Minimal&quot; Image<br>
    Minimal image is smaller, faster to boot and lightweight
</div>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-29-at-11.34.52-1.png" style="float:right; margin-left: 20px; width: 450px; height:225px" alt="Creating a Free (HTTPS) Blog on Oracle Cloud">
    Scroll Down<br>
    Change the VCN &amp; Subnet name to something meaningful<br>
    Save the Private Key so you can SSH into the VM<br>
    Click Create<br>
</div>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-05.26.26.png" style="float:right; margin-left: 20px; width: 300px; height:300px" alt="Creating a Free (HTTPS) Blog on Oracle Cloud">
    You&apos;ll be taken to the Instance Information page<br>
    Make a note of the Public IP<br>
    Click on the Subnet
</div>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-04.59.34.png" style="float:right; margin-left: 20px; width: 300px; height:150px" alt="Creating a Free (HTTPS) Blog on Oracle Cloud">
    You&apos;ll be taken to the Security List for your subnet<br>
    Click on the Default Security List<br>
    You&apos;ll be taken to the Firewall Rules Rules<br>
    Click Add Ingress Rules
</div>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-04.59.56.png" style="float:right; margin-left: 20px; width: 400px; height:300px" alt="Creating a Free (HTTPS) Blog on Oracle Cloud">
    Set the Source CIDR as 0.0.0.0/0<br>
    Set the Destination Port Range as 80,443<br><br>
    80 is required for SSL Cert setup,<br>
    443 is for HTTPS traffic to your site
</div>
<!--kg-card-end: html-->
<h3 id="create-configure-domain">Create &amp; Configure Domain</h3>
<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-05.27.46.png" style="float:right; margin-left: 20px; width: 300px; height:100px" alt="Creating a Free (HTTPS) Blog on Oracle Cloud">
    Head over to https://www.noip.com/ and grab your free domain
</div>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div>
    <img src="https://highoncloud.co.uk/content/images/2024/01/Screenshot-2021-01-24-at-05.29.25.png" style="float:right; margin-left: 20px; width: 350px; height:100px" alt="Creating a Free (HTTPS) Blog on Oracle Cloud">
    Create an A record in the DNS<br>
    Use the IP of your Free VM
</div>
<!--kg-card-end: html-->
<h3 id="install-ghost">Install Ghost</h3><p>SSH into your Free VM as the ubuntu user using the Public IP &amp; Private Key you downloaded during VM Creation</p><p>Now let&apos;s start with the Ghost setup installation.</p><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo apt upgrade
sudo apt update</code></pre><figcaption><p><span style="white-space: pre-wrap;">Perform an update of the packages</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo iptables -I INPUT 5 -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT 5 -p tcp --dport 443 -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4</code></pre><figcaption><p><span style="white-space: pre-wrap;">Open up the OS Firewall for ports 80 and 443</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo adduser ghostusr
sudo usermod -aG sudo ghostusr
sudo su - ghostusr</code></pre><figcaption><p><span style="white-space: pre-wrap;">Create a user for Ghost install, allow the user access to sudo</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo apt install cron
sudo apt-get install nginx
sudo apt-get install mysql-server</code></pre><figcaption><p><span style="white-space: pre-wrap;">Since this is Ubuntu Minimal it may not have cron package by default, so install cron as well as nginx and mysql</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo mysql</code></pre><figcaption><p><span style="white-space: pre-wrap;">Login to mysql</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-SQL">alter user &apos;root&apos;@&apos;localhost&apos; identified with mysql_native_password by &apos;oracle99&apos;;
quit;</code></pre><figcaption><p><span style="white-space: pre-wrap;">Set the root user password</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash
sudo apt-get install -y nodejs</code></pre><figcaption><p><span style="white-space: pre-wrap;">Install nodejs</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo npm install ghost-cli@latest -g</code></pre><figcaption><p><span style="white-space: pre-wrap;">Install Ghost CLI</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Bash">sudo mkdir -p /var/www/ghost
sudo chown ghostusr:ghostusr /var/www/ghost
sudo chmod 775 /var/www/ghost
cd /var/www/ghost</code></pre><figcaption><p><span style="white-space: pre-wrap;">Create a directory to host the Ghost Instance</span></p></figcaption></figure><p>Run the Ghost installation. You&apos;ll need to provide the URL, mysql details and an email address.</p><p>Answer Yes to all responses</p><pre><code class="language-Bash">ghost install 
&#x2714; Checking system Node.js version 
&#x2714; Checking logged in user 
&#x2714; Checking current folder permissions 
&#x2714; Checking system compatibility 
&#x2714; Checking for a MySQL installation 
&#x2714; Checking memory availability 
&#x2714; Checking free space 
&#x2714; Checking for latest Ghost version 
&#x2714; Setting up install directory 
&#x2714; Downloading and installing Ghost v3.40.5 
&#x2714; Finishing install process 
? Enter your blog URL: https://highoncloud.co.uk
? Enter your MySQL hostname: localhost 
? Enter your MySQL username: root 
? Enter your MySQL password: [hidden] 
? Enter your Ghost database name: ghostdb 
&#x2714; Configuring Ghost 
&#x2714; Setting up instance
sudo chown -R ghost:ghost /var/www/ghost/content 
	&#x2714; Setting up &quot;ghost&quot; system user 
	? Do you wish to set up &quot;ghost&quot; mysql user
	? Yes 
	&#x2714; Setting up &quot;ghost&quot; mysql user ? Do you wish to set up Nginx
	? Yes
sudo mv /tmp/highoncloud-co-uk/highoncloud.co.uk.conf /etc/nginx/sites-available/highoncloud.co.uk.conf
sudo ln -sf /etc/nginx/sites-available/highoncloud.co.uk.conf /etc/nginx/sites-enabled/highoncloud.co.uk.conf
sudo nginx -s reload 
	&#x2714; Setting up Nginx 
	? Do you wish to set up SSL? Yes 
	? Enter your email (For SSL Certificate) user@highoncloud.co.uk
sudo mkdir -p /etc/letsencrypt
sudo ./acme.sh --install --home /etc/letsencrypt
sudo /etc/letsencrypt/acme.sh --issue --home /etc/letsencrypt --domain highoncloud.co.uk --webroot /var/www/ghost/system/nginx-root --reloadcmd &quot;nginx -s reload&quot; --accountemail shahvaiz.janjua@hotmail.co.uk
sudo openssl dhparam -dsaparam -out /etc/nginx/snippets/dhparam.pem 2048
sudo mv /tmp/ssl-params.conf /etc/nginx/snippets/ssl-params.conf
sudo mv /tmp/highoncloud-co-uk/highoncloud.co.uk-ssl.conf /etc/nginx/sites-available/highoncloud.co.uk-ssl.conf
sudo ln -sf /etc/nginx/sites-available/highoncloud.co.uk-ssl.conf /etc/nginx/sites-enabled/highoncloud.co.uk-ssl.conf
sudo nginx -s reload 
	&#x2714; Setting up SSL 
	? Do you wish to set up Systemd? Yes
sudo mv /tmp/highoncloud-co-uk/ghost_highoncloud-co-uk.service /lib/systemd/system/ghost_highoncloud-co-uk.service
sudo systemctl daemon-reload 
	&#x2714; Setting up Systemd
sudo systemctl is-active ghost_highoncloud-co-uk 
	? Do you want to start Ghost? Yes
sudo systemctl start ghost_highoncloud-co-uk
sudo systemctl is-enabled ghost_highoncloud-co-uk
sudo systemctl enable ghost_highoncloud-co-uk --quiet &#x2714; Starting Ghost
Ghost uses direct mail by default. To set up an alternative email method read our docs at https://ghost.org/docs/concepts/config/#mail

Ghost was installed successfully! To complete setup of your publication, visit:
&lt;https://highoncloud.co.uk/ghost/&gt;</code></pre>]]></content:encoded></item></channel></rss>