mirror of
https://github.com/davegallant/davegallant.github.io.git
synced 2025-08-14 12:20:19 +00:00
1597 lines
48 KiB
HTML
1597 lines
48 KiB
HTML
<!doctype html><html lang=en data-theme=dark><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=preload as=font type=font/woff2 href=/fonts/roboto-slab-latin-400.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/roboto-slab-latin-700.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/fira-code-latin-300.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/fira-code-latin-400.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/fira-code-latin-700.woff2 crossorigin=anonymous><meta name=robots content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"><title>Using AKS and SOCKS to connect to a private Azure DB</title>
|
|
<meta name=description content="I ran into a roadblock recently where I wanted to be able to conveniently connect to a managed postgres database within Azure that was not running on public subnets. And by conveniently, I mean that I&rsquo;d rather not have to spin up an ephemeral virtual machine running in the same network and proxy the connection, and I&rsquo;d like to use a local client (preferably with a GUI). After several web searches, it became evident that Azure does not readily provide much tooling to support this.
|
|
"><link rel=canonical href=/blog/2023/05/22/using-aks-and-socks-to-connect-to-a-private-azure-db/><meta name=twitter:card content="summary"><meta name=twitter:title content="Using AKS and SOCKS to connect to a private Azure DB"><meta name=twitter:description content="I ran into a roadblock recently where I wanted to be able to conveniently connect to a managed postgres database within Azure that was not running on public subnets. And by conveniently, I mean that I’d rather not have to spin up an ephemeral virtual machine running in the same network and proxy the connection, and I’d like to use a local client (preferably with a GUI). After several web searches, it became evident that Azure does not readily provide much tooling to support this."><meta property="og:title" content="Using AKS and SOCKS to connect to a private Azure DB"><meta property="og:description" content="I ran into a roadblock recently where I wanted to be able to conveniently connect to a managed postgres database within Azure that was not running on public subnets. And by conveniently, I mean that I’d rather not have to spin up an ephemeral virtual machine running in the same network and proxy the connection, and I’d like to use a local client (preferably with a GUI). After several web searches, it became evident that Azure does not readily provide much tooling to support this."><meta property="og:type" content="article"><meta property="og:url" content="/blog/2023/05/22/using-aks-and-socks-to-connect-to-a-private-azure-db/"><meta property="article:section" content="post"><meta property="article:published_time" content="2023-05-22T16:31:29-04:00"><meta property="article:modified_time" content="2024-01-01T23:33:36-05:00"><meta itemprop=name content="Using AKS and SOCKS to connect to a private Azure DB"><meta itemprop=description content="I ran into a roadblock recently where I wanted to be able to conveniently connect to a managed postgres database within Azure that was not running on public subnets. And by conveniently, I mean that I’d rather not have to spin up an ephemeral virtual machine running in the same network and proxy the connection, and I’d like to use a local client (preferably with a GUI). After several web searches, it became evident that Azure does not readily provide much tooling to support this."><meta itemprop=datePublished content="2023-05-22T16:31:29-04:00"><meta itemprop=dateModified content="2024-01-01T23:33:36-05:00"><meta itemprop=wordCount content="619"><meta itemprop=keywords content="aks,aws,azure,bastion,cloud-sql-proxy,database,eks,k8s,kubectl-plugin-socks5-proxy,proxy,socat,socks,"><style>/*
|
|
Critical CSS for above-the-fold content, delivered inline to increase first
|
|
paint performance
|
|
*/
|
|
|
|
/*! purgecss start ignore */
|
|
|
|
/* Typefaces */
|
|
|
|
/* roboto-slab-100normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 100;
|
|
src:
|
|
local('Roboto Slab Thin '),
|
|
local('Roboto Slab-Thin'),
|
|
url('/fonts/roboto-slab-latin-100.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-100.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-200normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 200;
|
|
src:
|
|
local('Roboto Slab Extra Light '),
|
|
local('Roboto Slab-Extra Light'),
|
|
url('/fonts/roboto-slab-latin-200.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-200.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-300normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 300;
|
|
src:
|
|
local('Roboto Slab Light '),
|
|
local('Roboto Slab-Light'),
|
|
url('/fonts/roboto-slab-latin-300.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-300.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-400normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 400;
|
|
src:
|
|
local('Roboto Slab Regular '),
|
|
local('Roboto Slab-Regular'),
|
|
url('/fonts/roboto-slab-latin-400.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-400.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-500normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 500;
|
|
src:
|
|
local('Roboto Slab Medium '),
|
|
local('Roboto Slab-Medium'),
|
|
url('/fonts/roboto-slab-latin-500.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-500.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-600normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 600;
|
|
src:
|
|
local('Roboto Slab SemiBold '),
|
|
local('Roboto Slab-SemiBold'),
|
|
url('/fonts/roboto-slab-latin-600.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-600.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-700normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 700;
|
|
src:
|
|
local('Roboto Slab Bold '),
|
|
local('Roboto Slab-Bold'),
|
|
url('/fonts/roboto-slab-latin-700.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-700.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-800normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 800;
|
|
src:
|
|
local('Roboto Slab ExtraBold '),
|
|
local('Roboto Slab-ExtraBold'),
|
|
url('/fonts/roboto-slab-latin-800.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-800.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* roboto-slab-900normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Roboto Slab';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 900;
|
|
src:
|
|
local('Roboto Slab Black '),
|
|
local('Roboto Slab-Black'),
|
|
url('/fonts/roboto-slab-latin-900.woff2') format('woff2'),
|
|
url('/fonts/roboto-slab-latin-900.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* fira-code-300normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Fira Code';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 300;
|
|
src:
|
|
local('Fira Code Light '),
|
|
local('Fira Code-Light'),
|
|
url('/fonts/fira-code-latin-300.woff2') format('woff2'),
|
|
url('/fonts/fira-code-latin-300.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* fira-code-400normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Fira Code';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 400;
|
|
src:
|
|
local('Fira Code Regular '),
|
|
local('Fira Code-Regular'),
|
|
url('/fonts/fira-code-latin-400.woff2') format('woff2'),
|
|
url('/fonts/fira-code-latin-400.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* fira-code-500normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Fira Code';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 500;
|
|
src:
|
|
local('Fira Code Medium '),
|
|
local('Fira Code-Medium'),
|
|
url('/fonts/fira-code-latin-500.woff2') format('woff2'),
|
|
url('/fonts/fira-code-latin-500.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* fira-code-600normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Fira Code';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 600;
|
|
src:
|
|
local('Fira Code SemiBold '),
|
|
local('Fira Code-SemiBold'),
|
|
url('/fonts/fira-code-latin-600.woff2') format('woff2'),
|
|
url('/fonts/fira-code-latin-600.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* fira-code-700normal - latin */
|
|
|
|
@font-face {
|
|
font-family: 'Fira Code';
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 700;
|
|
src:
|
|
local('Fira Code Bold '),
|
|
local('Fira Code-Bold'),
|
|
url('/fonts/fira-code-latin-700.woff2') format('woff2'),
|
|
url('/fonts/fira-code-latin-700.woff') format('woff'); /* Modern Browsers */
|
|
}
|
|
|
|
/* Normalize */
|
|
|
|
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
|
|
|
/* Document
|
|
========================================================================== */
|
|
|
|
/**
|
|
* 1. Correct the line height in all browsers.
|
|
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
|
*/
|
|
|
|
html {
|
|
line-height: 1.15; /* 1 */
|
|
-webkit-text-size-adjust: 100%; /* 2 */
|
|
}
|
|
|
|
/* Sections
|
|
========================================================================== */
|
|
|
|
/**
|
|
* Remove the margin in all browsers.
|
|
*/
|
|
|
|
body {
|
|
margin: 0;
|
|
}
|
|
|
|
/**
|
|
* Render the `main` element consistently in IE.
|
|
*/
|
|
|
|
main {
|
|
display: block;
|
|
}
|
|
|
|
/**
|
|
* Correct the font size and margin on `h1` elements within `section` and
|
|
* `article` contexts in Chrome, Firefox, and Safari.
|
|
*/
|
|
|
|
h1 {
|
|
font-size: 2em;
|
|
margin: 0.67em 0;
|
|
}
|
|
|
|
/* Grouping content
|
|
========================================================================== */
|
|
|
|
/**
|
|
* 1. Add the correct box sizing in Firefox.
|
|
* 2. Show the overflow in Edge and IE.
|
|
*/
|
|
|
|
hr {
|
|
box-sizing: content-box; /* 1 */
|
|
height: 0; /* 1 */
|
|
overflow: visible; /* 2 */
|
|
}
|
|
|
|
/**
|
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
|
* 2. Correct the odd `em` font sizing in all browsers.
|
|
*/
|
|
|
|
pre {
|
|
font-family: monospace, monospace; /* 1 */
|
|
font-size: 1em; /* 2 */
|
|
}
|
|
|
|
/* Text-level semantics
|
|
========================================================================== */
|
|
|
|
/**
|
|
* Remove the gray background on active links in IE 10.
|
|
*/
|
|
|
|
a {
|
|
background-color: transparent;
|
|
}
|
|
|
|
/**
|
|
* 1. Remove the bottom border in Chrome 57-
|
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
|
*/
|
|
|
|
abbr[title] {
|
|
border-bottom: none; /* 1 */
|
|
text-decoration: underline; /* 2 */
|
|
text-decoration: underline dotted; /* 2 */
|
|
}
|
|
|
|
/**
|
|
* Add the correct font weight in Chrome, Edge, and Safari.
|
|
*/
|
|
|
|
b,
|
|
strong {
|
|
font-weight: bolder;
|
|
}
|
|
|
|
/**
|
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
|
* 2. Correct the odd `em` font sizing in all browsers.
|
|
*/
|
|
|
|
code,
|
|
kbd,
|
|
samp {
|
|
font-family: monospace, monospace; /* 1 */
|
|
font-size: 1em; /* 2 */
|
|
}
|
|
|
|
/**
|
|
* Add the correct font size in all browsers.
|
|
*/
|
|
|
|
small {
|
|
font-size: 80%;
|
|
}
|
|
|
|
/**
|
|
* Prevent `sub` and `sup` elements from affecting the line height in
|
|
* all browsers.
|
|
*/
|
|
|
|
sub,
|
|
sup {
|
|
font-size: 75%;
|
|
line-height: 0;
|
|
position: relative;
|
|
vertical-align: baseline;
|
|
}
|
|
|
|
sub {
|
|
bottom: -0.25em;
|
|
}
|
|
|
|
sup {
|
|
top: -0.5em;
|
|
}
|
|
|
|
/* Embedded content
|
|
========================================================================== */
|
|
|
|
/**
|
|
* Remove the border on images inside links in IE 10.
|
|
*/
|
|
|
|
img {
|
|
border-style: none;
|
|
}
|
|
|
|
/* Forms
|
|
========================================================================== */
|
|
|
|
/**
|
|
* 1. Change the font styles in all browsers.
|
|
* 2. Remove the margin in Firefox and Safari.
|
|
*/
|
|
|
|
button,
|
|
input,
|
|
optgroup,
|
|
select,
|
|
textarea {
|
|
font-family: inherit; /* 1 */
|
|
font-size: 100%; /* 1 */
|
|
line-height: 1.15; /* 1 */
|
|
margin: 0; /* 2 */
|
|
}
|
|
|
|
/**
|
|
* Show the overflow in IE.
|
|
* 1. Show the overflow in Edge.
|
|
*/
|
|
|
|
button,
|
|
input { /* 1 */
|
|
overflow: visible;
|
|
}
|
|
|
|
/**
|
|
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
|
* 1. Remove the inheritance of text transform in Firefox.
|
|
*/
|
|
|
|
button,
|
|
select { /* 1 */
|
|
text-transform: none;
|
|
}
|
|
|
|
/**
|
|
* Correct the inability to style clickable types in iOS and Safari.
|
|
*/
|
|
|
|
button,
|
|
[type="button"],
|
|
[type="reset"],
|
|
[type="submit"] {
|
|
-webkit-appearance: button;
|
|
}
|
|
|
|
/**
|
|
* Remove the inner border and padding in Firefox.
|
|
*/
|
|
|
|
button::-moz-focus-inner,
|
|
[type="button"]::-moz-focus-inner,
|
|
[type="reset"]::-moz-focus-inner,
|
|
[type="submit"]::-moz-focus-inner {
|
|
border-style: none;
|
|
padding: 0;
|
|
}
|
|
|
|
/**
|
|
* Restore the focus styles unset by the previous rule.
|
|
*/
|
|
|
|
button:-moz-focusring,
|
|
[type="button"]:-moz-focusring,
|
|
[type="reset"]:-moz-focusring,
|
|
[type="submit"]:-moz-focusring {
|
|
outline: 1px dotted ButtonText;
|
|
}
|
|
|
|
/**
|
|
* Correct the padding in Firefox.
|
|
*/
|
|
|
|
fieldset {
|
|
padding: 0.35em 0.75em 0.625em;
|
|
}
|
|
|
|
/**
|
|
* 1. Correct the text wrapping in Edge and IE.
|
|
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
|
* 3. Remove the padding so developers are not caught out when they zero out
|
|
* `fieldset` elements in all browsers.
|
|
*/
|
|
|
|
legend {
|
|
box-sizing: border-box; /* 1 */
|
|
color: inherit; /* 2 */
|
|
display: table; /* 1 */
|
|
max-width: 100%; /* 1 */
|
|
padding: 0; /* 3 */
|
|
white-space: normal; /* 1 */
|
|
}
|
|
|
|
/**
|
|
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
|
*/
|
|
|
|
progress {
|
|
vertical-align: baseline;
|
|
}
|
|
|
|
/**
|
|
* Remove the default vertical scrollbar in IE 10+.
|
|
*/
|
|
|
|
textarea {
|
|
overflow: auto;
|
|
}
|
|
|
|
/**
|
|
* 1. Add the correct box sizing in IE 10.
|
|
* 2. Remove the padding in IE 10.
|
|
*/
|
|
|
|
[type="checkbox"],
|
|
[type="radio"] {
|
|
box-sizing: border-box; /* 1 */
|
|
padding: 0; /* 2 */
|
|
}
|
|
|
|
/**
|
|
* Correct the cursor style of increment and decrement buttons in Chrome.
|
|
*/
|
|
|
|
[type="number"]::-webkit-inner-spin-button,
|
|
[type="number"]::-webkit-outer-spin-button {
|
|
height: auto;
|
|
}
|
|
|
|
/**
|
|
* 1. Correct the odd appearance in Chrome and Safari.
|
|
* 2. Correct the outline style in Safari.
|
|
*/
|
|
|
|
[type="search"] {
|
|
-webkit-appearance: textfield; /* 1 */
|
|
outline-offset: -2px; /* 2 */
|
|
}
|
|
|
|
/**
|
|
* Remove the inner padding in Chrome and Safari on macOS.
|
|
*/
|
|
|
|
[type="search"]::-webkit-search-decoration {
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
/**
|
|
* 1. Correct the inability to style clickable types in iOS and Safari.
|
|
* 2. Change font properties to `inherit` in Safari.
|
|
*/
|
|
|
|
::-webkit-file-upload-button {
|
|
-webkit-appearance: button; /* 1 */
|
|
font: inherit; /* 2 */
|
|
}
|
|
|
|
/* Interactive
|
|
========================================================================== */
|
|
|
|
/*
|
|
* Add the correct display in Edge, IE 10+, and Firefox.
|
|
*/
|
|
|
|
details {
|
|
display: block;
|
|
}
|
|
|
|
/*
|
|
* Add the correct display in all browsers.
|
|
*/
|
|
|
|
summary {
|
|
display: list-item;
|
|
}
|
|
|
|
/* Misc
|
|
========================================================================== */
|
|
|
|
/**
|
|
* Add the correct display in IE 10+.
|
|
*/
|
|
|
|
template {
|
|
display: none;
|
|
}
|
|
|
|
/**
|
|
* Add the correct display in IE 10.
|
|
*/
|
|
|
|
[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/*! purgecss end ignore */
|
|
|
|
/*! CC BY-SA 3.0 License | https://stackoverflow.com/a/36118384/1154965 */
|
|
|
|
@keyframes blink {
|
|
50% {
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* Default license header for non-vendor CSS source code that follows */
|
|
|
|
/*! MIT License | github.com/schnerring/hugo-theme-gruvbox */
|
|
|
|
/*
|
|
Bootstrap 5 breakpoints
|
|
See: https://getbootstrap.com/docs/5.0/layout/breakpoints/#available-breakpoints
|
|
*/
|
|
|
|
@custom-media --sm (min-width: 576px);
|
|
|
|
@custom-media --md (min-width: 768px);
|
|
|
|
@custom-media --lg (min-width: 992px);
|
|
|
|
@custom-media --xl (min-width: 1200px);
|
|
|
|
@custom-media --xxl (min-width: 1400px);
|
|
|
|
:root[data-theme="light"] {
|
|
--bg: var(--bg0);
|
|
--bg0: #fbf1c7;
|
|
--bg0_h: #f9f5d7;
|
|
--bg0_s: #f2e5bc;
|
|
--bg1: #ebdbb2;
|
|
--bg2: #d5c4a1;
|
|
--bg3: #bdae93;
|
|
--bg4: #a89984;
|
|
--fg: var(--fg1);
|
|
--fg0: #282828;
|
|
--fg1: #3c3836;
|
|
--fg2: #504945;
|
|
--fg3: #665c54;
|
|
--fg4: #7c6f64;
|
|
--gray1: var(--fg4);
|
|
--gray2: #928374;
|
|
--red1: #cc241d;
|
|
--red2: #9d0006;
|
|
--green1: #98971a;
|
|
--green2: #797403;
|
|
--yellow1: #d79921;
|
|
--yellow2: #b57614;
|
|
--blue1: #458588;
|
|
--blue2: #076678;
|
|
--purple1: #b16286;
|
|
--purple2: #8f3f71;
|
|
--aqua1: #689d6a;
|
|
--aqua2: #427b58;
|
|
--orange1: #d65d0e;
|
|
--orange2: #af3a03;
|
|
}
|
|
|
|
[data-theme="light"]:root .light--hidden {
|
|
display: none;
|
|
}
|
|
|
|
:root[data-theme="dark"] {
|
|
--bg: var(--bg0);
|
|
--bg0: #282828;
|
|
--bg0_h: #1d2021;
|
|
--bg0_s: #32302f;
|
|
--bg1: #3c3836;
|
|
--bg2: #504945;
|
|
--bg3: #665c54;
|
|
--bg4: #7c6f64;
|
|
--fg: var(--fg1);
|
|
--fg0: #fbf1c7;
|
|
--fg1: #ebdbb2;
|
|
--fg2: #d5c4a1;
|
|
--fg3: #bdae93;
|
|
--fg4: #a89984;
|
|
--gray1: var(--fg4);
|
|
--gray2: #928374;
|
|
--red1: #cc241d;
|
|
--red2: #fb4934;
|
|
--green1: #98971a;
|
|
--green2: #b8bb26;
|
|
--yellow1: #d79921;
|
|
--yellow2: #fabd2f;
|
|
--blue1: #458588;
|
|
--blue2: #83a598;
|
|
--purple1: #b16286;
|
|
--purple2: #d3869b;
|
|
--aqua1: #689d6a;
|
|
--aqua2: #8ec07c;
|
|
--orange1: #d65d0e;
|
|
--orange2: #fe8019;
|
|
}
|
|
|
|
[data-theme="dark"]:root .dark--hidden {
|
|
display: none;
|
|
}
|
|
|
|
:root {
|
|
|
|
--primary: var(--blue1);
|
|
--primary-alt: var(--blue2);
|
|
}
|
|
|
|
:root {
|
|
--font-monospace: "Fira Code", "Lucida Console", Monaco, monospace;
|
|
--font-sans-serif: Verdana, Helvetica, sans-serif;
|
|
--font-serif: "Roboto Slab", Georgia, serif;
|
|
}
|
|
|
|
html {
|
|
font-family: var(--font-serif);
|
|
font-size: 1rem;
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--fg);
|
|
line-height: 1.675;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
strong {
|
|
letter-spacing: 0.35px;
|
|
}
|
|
|
|
a {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
a.link--external::after {
|
|
/* 2009 = Thin Space */
|
|
content: "\2009↗";
|
|
}
|
|
|
|
img,
|
|
video {
|
|
border: 2px solid var(--bg1);
|
|
height: auto;
|
|
max-width: 100%;
|
|
}
|
|
|
|
figure {
|
|
display: inline-block;
|
|
}
|
|
|
|
figcaption {
|
|
color: var(--fg3);
|
|
font-family: var(--font-serif);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
*::selection {
|
|
color: var(--fg0);
|
|
background: var(--bg4);
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3,
|
|
h4,
|
|
h5,
|
|
h6 {
|
|
color: var(--fg0);
|
|
font-family: var(--font-monospace);
|
|
font-weight: 300;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
|
font-size: 1em;
|
|
}
|
|
|
|
h2,
|
|
h3,
|
|
h4,
|
|
h5,
|
|
h6 {
|
|
border-bottom: 1px solid var(--bg1);
|
|
}
|
|
|
|
h1,
|
|
h2 {
|
|
font-weight: 400;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.875rem;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.75rem;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 1.625rem;
|
|
}
|
|
|
|
@media (--md) {
|
|
h1 {
|
|
font-size: 2.375rem;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 1.75rem;
|
|
}
|
|
}
|
|
|
|
h4 {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
h5 {
|
|
font-size: 1.375rem;
|
|
}
|
|
|
|
h6 {
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
table {
|
|
table-layout: fixed;
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin: 2rem 0;
|
|
}
|
|
|
|
table,
|
|
th,
|
|
td {
|
|
border: 1px solid var(--bg1);
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
hr {
|
|
border: none;
|
|
background: var(--bg1);
|
|
height: 1px;
|
|
margin: 3rem auto;
|
|
width: 80%;
|
|
}
|
|
|
|
mark {
|
|
background: var(--yellow1);
|
|
color: var(--bg0);
|
|
}
|
|
|
|
abbr {
|
|
text-underline-offset: 0.2rem;
|
|
}
|
|
|
|
blockquote,
|
|
code,
|
|
kbd,
|
|
mark,
|
|
pre {
|
|
border-radius: 0.2rem;
|
|
padding: 0 0.2em;
|
|
}
|
|
|
|
pre code {
|
|
padding: 0;
|
|
}
|
|
|
|
blockquote,
|
|
code,
|
|
kbd,
|
|
pre,
|
|
th {
|
|
background: var(--bg1);
|
|
}
|
|
|
|
code,
|
|
kbd,
|
|
pre,
|
|
th {
|
|
font-family: var(--font-monospace);
|
|
}
|
|
|
|
code code,
|
|
kbd code,
|
|
code kbd,
|
|
kbd kbd {
|
|
background: var(--bg2);
|
|
}
|
|
|
|
blockquote,
|
|
pre {
|
|
padding: 1rem;
|
|
}
|
|
|
|
pre {
|
|
/* TODO is !important really needed because of Prism? */
|
|
background: var(--bg1) !important;
|
|
overflow: auto;
|
|
}
|
|
|
|
pre code {
|
|
background: none;
|
|
}
|
|
|
|
blockquote,
|
|
blockquote.twitter-tweet {
|
|
border-left: var(--primary-alt) 5px solid;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
:is(blockquote,blockquote.twitter-tweet) code {
|
|
background: var(--bg2);
|
|
}
|
|
|
|
:is(blockquote,blockquote.twitter-tweet) p:first-of-type {
|
|
margin-top: 0;
|
|
}
|
|
|
|
:is(blockquote,blockquote.twitter-tweet) p:last-of-type {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
blockquote.twitter-tweet {
|
|
border-color: var(--blue2);
|
|
color: inherit;
|
|
font: inherit;
|
|
font-size: inherit;
|
|
line-height: inherit;
|
|
}
|
|
|
|
blockquote.twitter-tweet a {
|
|
color: var(--blue2);
|
|
}
|
|
|
|
blockquote.twitter-tweet a:hover {
|
|
color: var(--blue1);
|
|
text-decoration: none !important;
|
|
}
|
|
|
|
pre::-webkit-scrollbar {
|
|
height: 0.5rem;
|
|
scrollbar-width: auto;
|
|
}
|
|
|
|
pre::-webkit-scrollbar-track {
|
|
background: var(--bg2);
|
|
border-radius: 0.2rem;
|
|
}
|
|
|
|
pre::-webkit-scrollbar-thumb {
|
|
background: var(--bg4);
|
|
border-radius: 0.2rem;
|
|
}
|
|
|
|
.layout {
|
|
display: grid;
|
|
grid-template-areas:
|
|
"header"
|
|
"main"
|
|
"footer";
|
|
grid-template-rows: auto 1fr auto;
|
|
height: 100vh;
|
|
}
|
|
|
|
main {
|
|
align-items: start;
|
|
display: grid;
|
|
grid-area: main;
|
|
grid-template-areas: "empty content sidebar";
|
|
grid-template-columns: 1fr minmax(0, 650px) 4fr;
|
|
}
|
|
|
|
header {
|
|
background: var(--bg1);
|
|
grid-area: header;
|
|
}
|
|
|
|
footer {
|
|
grid-area: footer;
|
|
}
|
|
|
|
main,
|
|
footer {
|
|
margin: 0.5em 1.1em;
|
|
}
|
|
|
|
.content {
|
|
grid-area: content;
|
|
}
|
|
|
|
.sidebar {
|
|
display: none;
|
|
flex-direction: column;
|
|
grid-area: sidebar;
|
|
margin-top: 3rem;
|
|
position: sticky;
|
|
top: 2rem;
|
|
}
|
|
|
|
@media (--lg) {
|
|
|
|
.sidebar {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
header {
|
|
display: grid;
|
|
font-family: var(--font-monospace);
|
|
font-size: 1.125rem;
|
|
grid-template-columns: auto auto 1fr auto;
|
|
grid-template-areas: "heading search nav theme-toggle";
|
|
padding: 0.75rem;
|
|
}
|
|
|
|
.logo {
|
|
color: var(--fg0);
|
|
display: flex;
|
|
font-weight: 700;
|
|
grid-area: heading;
|
|
}
|
|
|
|
.logo:hover .logo__cursor {
|
|
animation: 1s blink infinite;
|
|
opacity: 1;
|
|
}
|
|
|
|
.logo__chevron,
|
|
.logo__cursor {
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.logo__cursor {
|
|
opacity: 0;
|
|
}
|
|
|
|
.logo__text {
|
|
display: none;
|
|
}
|
|
|
|
@media (--md) {
|
|
.logo__text {
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
/*! purgecss start ignore */
|
|
|
|
.search {
|
|
display: flex;
|
|
grid-area: search;
|
|
margin: 0 1rem;
|
|
}
|
|
|
|
#search__text {
|
|
border: 1px solid var(--bg2);
|
|
border-radius: 0.2rem;
|
|
background: var(--bg2);
|
|
caret-color: var(--fg);
|
|
color: var(--fg);
|
|
outline: none;
|
|
padding: 0 0.5rem;
|
|
width: 100%;
|
|
}
|
|
|
|
#search__text:hover {
|
|
border-color: var(--bg3);
|
|
}
|
|
|
|
#search__text:focus {
|
|
border-color: var(--bg4);
|
|
}
|
|
|
|
#search__text::placeholder {
|
|
color: var(--fg3);
|
|
}
|
|
|
|
#search__text[type="search"]::-webkit-search-cancel-button {
|
|
appearance: none;
|
|
}
|
|
|
|
#search__suggestions {
|
|
background: var(--bg);
|
|
border-radius: 0.2rem;
|
|
box-shadow: 0 0.5rem 1rem var(--bg1);
|
|
font-family: var(--font-serif);
|
|
left: 0;
|
|
margin-top: 2rem;
|
|
position: absolute;
|
|
width: 95vw;
|
|
z-index: 1000;
|
|
}
|
|
|
|
@media (--md) {
|
|
.search {
|
|
position: relative;
|
|
}
|
|
|
|
#search__suggestions {
|
|
width: 60vw;
|
|
}
|
|
}
|
|
|
|
.search__suggestions--hidden {
|
|
display: none;
|
|
}
|
|
|
|
.search__suggestion-item {
|
|
border-bottom: 1px dashed var(--bg2);
|
|
display: grid;
|
|
grid-template-columns: 1fr 2fr;
|
|
}
|
|
|
|
.search__suggestion-item:focus,
|
|
.search__suggestion-item:focus-visible,
|
|
.search__suggestion-item:hover {
|
|
background: var(--bg1);
|
|
cursor: pointer;
|
|
outline: none;
|
|
}
|
|
|
|
.search__suggestion-item:last-child {
|
|
border: none;
|
|
}
|
|
|
|
.search__suggestion-title,
|
|
.search__suggestion-description {
|
|
padding: 0 1rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.search__suggestion-title {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.search__suggestion-description {
|
|
border-left: 1px solid var(--bg2);
|
|
}
|
|
|
|
.search__no-results {
|
|
padding: 0.75rem;
|
|
}
|
|
|
|
/*! purgecss end ignore */
|
|
|
|
.theme__toggle {
|
|
align-items: center;
|
|
background: none;
|
|
border: none;
|
|
color: var(--yellow1);
|
|
cursor: pointer;
|
|
display: flex;
|
|
grid-area: theme-toggle;
|
|
margin: 0 1rem;
|
|
}
|
|
|
|
.theme__toggle:hover {
|
|
color: var(--yellow2);
|
|
}
|
|
|
|
.theme__toggle svg {
|
|
height: 28px;
|
|
width: 28px;
|
|
}
|
|
|
|
/* TODO: simplify deep nesting */
|
|
|
|
nav#menu {
|
|
align-items: center;
|
|
display: flex;
|
|
grid-area: nav;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
nav#menu .menu__item {
|
|
color: var(--fg);
|
|
}
|
|
|
|
nav#menu .menu__item:hover {
|
|
color: var(--fg3);
|
|
cursor: pointer;
|
|
}
|
|
|
|
nav#menu ul {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
nav#menu ul.menu--horizontal {
|
|
align-items: center;
|
|
display: none;
|
|
}
|
|
|
|
nav#menu ul.menu--horizontal li {
|
|
display: inline-block;
|
|
margin: 0 0.75rem;
|
|
}
|
|
|
|
@media (--md) {
|
|
|
|
nav#menu ul.menu--horizontal {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
nav#menu ul.menu--vertical {
|
|
background: var(--fg0);
|
|
bottom: 0;
|
|
margin: 0;
|
|
padding: 3rem;
|
|
position: fixed;
|
|
right: 0;
|
|
top: 0;
|
|
transform: translate(100%, 0);
|
|
transition: transform 0.5s cubic-bezier(0.9, 0, 0.1, 1);
|
|
width: 50%;
|
|
z-index: 10;
|
|
}
|
|
|
|
nav#menu ul.menu--vertical .menu__item {
|
|
color: var(--bg1);
|
|
}
|
|
|
|
nav#menu ul.menu--vertical .menu__item:hover {
|
|
color: var(--bg4);
|
|
}
|
|
|
|
nav#menu .menu__burger {
|
|
display: flex;
|
|
height: 24px;
|
|
width: 24px;
|
|
}
|
|
|
|
nav#menu .menu__burger > * {
|
|
position: absolute;
|
|
}
|
|
|
|
nav#menu .menu__burger svg {
|
|
width: inherit;
|
|
z-index: 20;
|
|
height: inherit;
|
|
}
|
|
|
|
nav#menu .menu__burger svg line {
|
|
transition-duration: 0.5s;
|
|
transition-property: stroke, opacity, transform;
|
|
transition-timing-function: cubic-bezier(0.9, 0, 0.1, 1);
|
|
}
|
|
|
|
nav#menu .menu__burger svg line:nth-of-type(1) {
|
|
transform-origin: center 6px;
|
|
}
|
|
|
|
nav#menu .menu__burger svg line:nth-of-type(2) {
|
|
transform-origin: center 12px;
|
|
}
|
|
|
|
nav#menu .menu__burger svg line:nth-of-type(3) {
|
|
transform-origin: center 18px;
|
|
}
|
|
|
|
nav#menu .menu__burger input {
|
|
height: inherit;
|
|
opacity: 0;
|
|
width: inherit;
|
|
z-index: 30;
|
|
}
|
|
|
|
:is(nav#menu .menu__burger input:checked) ~ ul.menu--vertical {
|
|
transform: none;
|
|
}
|
|
|
|
:is(nav#menu .menu__burger input:checked) ~ svg {
|
|
stroke: var(--bg1);
|
|
}
|
|
|
|
:is(nav#menu .menu__burger input:checked) ~ svg line:nth-of-type(1) {
|
|
transform: translate(0, 6px) rotate(45deg);
|
|
}
|
|
|
|
:is(nav#menu .menu__burger input:checked) ~ svg line:nth-of-type(2) {
|
|
opacity: 0;
|
|
transform: scale(0.2);
|
|
}
|
|
|
|
:is(nav#menu .menu__burger input:checked) ~ svg line:nth-of-type(3) {
|
|
transform: translate(0, -6px) rotate(-45deg);
|
|
}
|
|
|
|
@media (--md) {
|
|
|
|
nav#menu .menu__burger {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.sidebar {
|
|
font-family: var(--font-monospace);
|
|
max-width: 350px;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
padding-left: 2.5rem;
|
|
}
|
|
|
|
.sidebar hr {
|
|
margin: 1.5rem auto;
|
|
}
|
|
|
|
.sidebar svg {
|
|
fill: var(--fg);
|
|
}
|
|
|
|
.sidebar__heading {
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
aside.toc a {
|
|
color: var(--primary-alt);
|
|
}
|
|
|
|
aside.toc a:hover {
|
|
color: var(--primary);
|
|
}
|
|
|
|
aside.toc ul {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
aside.toc ul ul {
|
|
font-size: 0.9rem;
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
aside.toc ul li {
|
|
line-height: 1.1;
|
|
}
|
|
|
|
aside.toc ul li a {
|
|
display: block;
|
|
padding: 0.2rem 0;
|
|
}
|
|
|
|
.jr-basics__image {
|
|
background: var(--bg1);
|
|
border: 2px solid var(--bg2);
|
|
}
|
|
|
|
.jr-basics__summary {
|
|
color: var(--fg3);
|
|
font-family: var(--font-serif);
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.jr-basics__profile a:hover {
|
|
color: var(--fg3);
|
|
}
|
|
|
|
.jr-basics__profile a:hover svg {
|
|
fill: var(--fg3);
|
|
}
|
|
|
|
.tag-cloud {
|
|
line-height: 1.1;
|
|
text-align: justify;
|
|
}
|
|
|
|
.tag-cloud__tag:hover {
|
|
color: var(--fg3);
|
|
}
|
|
|
|
.tag-cloud__tag--active {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.post,
|
|
.content-section {
|
|
border-bottom: 2px dotted var(--bg1);
|
|
padding: 2rem 0;
|
|
}
|
|
|
|
.post figure,
|
|
.post img:not(figure img),
|
|
.post video:not(figure video) {
|
|
margin: 0.5rem 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.post-header,
|
|
.post-content__read-more {
|
|
font-family: var(--font-monospace);
|
|
}
|
|
|
|
.post-meta__author {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.post-content {
|
|
margin: 1.3rem 0;
|
|
}
|
|
|
|
.post-content__read-more {
|
|
color: var(--primary-alt);
|
|
margin-top: 1.3rem;
|
|
}
|
|
|
|
.post-header a, .post-content a {
|
|
color: var(--primary-alt);
|
|
}
|
|
|
|
.post-header a:hover, .post-content a:hover {
|
|
color: var(--primary);
|
|
}
|
|
|
|
.post-tags {
|
|
align-items: center;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.9rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.post-tag {
|
|
font-size: 0.9rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
.post-tag::before {
|
|
content: "#";
|
|
}
|
|
|
|
.post-heading__anchor {
|
|
display: none;
|
|
}
|
|
|
|
h1:hover .post-heading__anchor, h2:hover .post-heading__anchor, h3:hover .post-heading__anchor, h4:hover .post-heading__anchor, h5:hover .post-heading__anchor, h6:hover .post-heading__anchor {
|
|
display: inline-block;
|
|
}
|
|
|
|
.jr__item-meta {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.jr__item-meta,
|
|
.jr-basics__image,
|
|
.jr-basics__item,
|
|
.jr-basics__profile-icon,
|
|
.jr-basics__profile-item {
|
|
align-items: center;
|
|
display: flex;
|
|
}
|
|
|
|
.jr-awards__title,
|
|
.jr-certificates__name,
|
|
.jr-education__area,
|
|
.jr-projects__roles,
|
|
.jr-publications__name,
|
|
.jr-volunteer__position,
|
|
.jr-work__position,
|
|
.jr-basics__name {
|
|
font-size: 1.125rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.jr-basics__item {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
}
|
|
|
|
.jr-basics__item hr {
|
|
margin: 1.5rem auto;
|
|
}
|
|
|
|
.jr-basics__image {
|
|
border-radius: 50%;
|
|
height: 250px;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
width: 250px;
|
|
}
|
|
|
|
.jr-basics__name,
|
|
.jr-basics__label,
|
|
.jr-basics__summary {
|
|
margin-top: 0.75rem;
|
|
}
|
|
|
|
.jr-basics__profile svg {
|
|
height: 24px;
|
|
width: 24px;
|
|
}
|
|
|
|
.jr-basics__profile,
|
|
.jr-basics__profile-item {
|
|
display: flex;
|
|
}
|
|
|
|
.jr-basics__profile-item {
|
|
display: flex;
|
|
padding: 0.2rem;
|
|
}
|
|
|
|
.jr-basics__profile--col {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.jr-basics__profile--row {
|
|
flex-wrap: wrap;
|
|
justify-content: space-evenly;
|
|
}
|
|
|
|
.jr-basics__profile-icon {
|
|
padding: 0 0.75rem;
|
|
}
|
|
|
|
.jr__item-meta {
|
|
font-family: var(--font-monospace);
|
|
}
|
|
|
|
.jr__item-meta {
|
|
align-items: start;
|
|
flex-flow: column;
|
|
}
|
|
|
|
@media (--md) {
|
|
.jr__item-meta {
|
|
align-items: center;
|
|
flex-flow: row wrap;
|
|
}
|
|
|
|
.jr__date,
|
|
.jr__date-range,
|
|
.jr-work__location {
|
|
flex-grow: 1;
|
|
text-align: right;
|
|
}
|
|
|
|
.jr-awards__awarder,
|
|
.jr-publications__publisher,
|
|
.jr-education__institution,
|
|
.jr-volunteer__organization {
|
|
flex-basis: 100%;
|
|
}
|
|
}
|
|
|
|
.social-share {
|
|
align-items: center;
|
|
border-top: 2px dotted var(--bg1);
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.9rem;
|
|
margin: 3rem 0;
|
|
padding-top: 3rem;
|
|
}
|
|
|
|
.social-share svg {
|
|
fill: var(--fg);
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.social-share svg.icon-tabler {
|
|
fill: none;
|
|
stroke: var(--fg);
|
|
}
|
|
|
|
.social-share__item {
|
|
background: var(--bg1);
|
|
padding: 0.5rem;
|
|
display: flex;
|
|
}
|
|
|
|
/* Sticky social bar left
|
|
|
|
@media (--xl) {
|
|
.social-share {
|
|
align-items: flex-start;
|
|
border-top: none;
|
|
flex-direction: column;
|
|
|
|
position: fixed;
|
|
top: 15%;
|
|
left: 0;
|
|
}
|
|
|
|
.social-share__heading {
|
|
display: none;
|
|
}
|
|
|
|
.social-share__item {
|
|
transition: padding 0.2s ease-in;
|
|
|
|
&:hover {
|
|
padding: 0.5rem 1rem;
|
|
}
|
|
|
|
& svg {
|
|
&:hover {
|
|
fill: var(--fg);
|
|
}
|
|
|
|
&.icon-tabler {
|
|
fill: none;
|
|
|
|
&:hover {
|
|
stroke: var(--fg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*/
|
|
</style><link rel=preload href="/css/non-critical.67cb283542b48f68e77ea69ecc88c660d6cd3c5286d9f66a72c693a6d009b5745c627a5b065dab9c3583e47513e8845d4c7aa57bd4c4847fa4fca61ae5258681.css" as=style onload='this.onload=null,this.rel="stylesheet"' integrity="sha512-Z8soNUK0j2jnfqaezIjGYNbNPFKG2fZqcsaTptAJtXRcYnpbBl2rnDWD5HUT6IRdTHqle9TEhH+k/KYa5SWGgQ=="><link id=prism-dark rel=preload href=/prism-themes/prism-gruvbox-dark.min.54aecc64074623a4f9898544dcbdab9e804f1560ef0b38f4cf8e10fcaaf72264e798cb407c601aca6ecd833ec4eb93d66535581f18d45ba202cf848b70dbc332.css as=style onload='this.onload=null,this.rel="stylesheet"' integrity="sha512-VK7MZAdGI6T5iYVE3L2rnoBPFWDvCzj0z44Q/Kr3ImTnmMtAfGAaym7Ngz7E65PWZTVYHxjUW6ICz4SLcNvDMg=="><link id=prism-light rel=preload href=/prism-themes/prism-gruvbox-light.min.42a221741efe997fcc94187c39d63c555560678789ac9ca856c74a5f0ddb2aa6c50d38b2ffbecc7a99038cbbd2efa99746e862267f781c559e0cfec10b88a5fc.css as=style onload='this.onload=null,this.rel="stylesheet"' integrity="sha512-QqIhdB7+mX/MlBh8OdY8VVVgZ4eJrJyoVsdKXw3bKqbFDTiy/77MepkDjLvS76mXRuhiJn94HFWeDP7BC4il/A==" disabled><noscript><link rel=stylesheet href=/prism-themes/prism-gruvbox-dark.min.54aecc64074623a4f9898544dcbdab9e804f1560ef0b38f4cf8e10fcaaf72264e798cb407c601aca6ecd833ec4eb93d66535581f18d45ba202cf848b70dbc332.css integrity="sha512-VK7MZAdGI6T5iYVE3L2rnoBPFWDvCzj0z44Q/Kr3ImTnmMtAfGAaym7Ngz7E65PWZTVYHxjUW6ICz4SLcNvDMg=="><link rel=stylesheet href="/css/non-critical.67cb283542b48f68e77ea69ecc88c660d6cd3c5286d9f66a72c693a6d009b5745c627a5b065dab9c3583e47513e8845d4c7aa57bd4c4847fa4fca61ae5258681.css" integrity="sha512-Z8soNUK0j2jnfqaezIjGYNbNPFKG2fZqcsaTptAJtXRcYnpbBl2rnDWD5HUT6IRdTHqle9TEhH+k/KYa5SWGgQ=="></noscript><script>(()=>{function n(){if(localStorage&&localStorage.getItem("theme"))return localStorage.getItem("theme");if(window.matchMedia)return window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}function e(e){document.documentElement.setAttribute("data-theme",e);let t=document.getElementById("prism-dark"),n=document.getElementById("prism-light");t.toggleAttribute("disabled",e==="light"),n.toggleAttribute("disabled",e==="dark"),localStorage.setItem("theme",e)}var t=n();t&&e(t);function s(t){let n=t.currentTarget.classList.contains("light--hidden")?"light":"dark";e(n)}document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(".theme__toggle").forEach(e=>{e.addEventListener("click",s)})})})()</script><link rel=apple-touch-icon sizes=180x180 href=/apple-touch-icon.png><link rel=icon type=image/png sizes=32x32 href=/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=/favicon-16x16.png><link rel=manifest href=/site.webmanifest><link rel=mask-icon href=/safari-pinned-tab.svg color=#282828><meta name=msapplication-TileColor content="#282828"><meta name=theme-color content="#282828"></head><body><div class=layout><header><a class=logo href=/><div class=logo__text>davegallant.ca</div><div class=logo__chevron>></div><div class=logo__cursor>█</div></a><div class=search><input id=search__text type=search placeholder=Search... aria-label=Search autocomplete=off><div id=search__suggestions class=search__suggestions--hidden></div></div><nav id=menu><ul class=menu--horizontal><li class=menu__item><a href=/index.xml>RSS</a></li></ul><div class=menu__burger><input class=menu__item type=checkbox aria-label="Open main menu"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-menu-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/></svg><ul class=menu--vertical><li><a class=menu__item href=/index.xml>RSS</a></li></ul></div></nav><button class="theme__toggle light--hidden" aria-label="Toggle light mode">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-sun" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-4 0a4 4 0 108 0 4 4 0 10-8 0"/><path d="M3 12h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7-.7.7m0 11.4.7.7m-12.1-.7-.7.7"/></svg></button>
|
|
<button class="theme__toggle dark--hidden" aria-label="Toggle dark mode"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3c.132.0.263.0.393.0a7.5 7.5.0 007.92 12.446A9 9 0 1112 2.992z"/></svg></button></header><main><div class=content><article class=post><div class=post-header><h1>Using AKS and SOCKS to connect to a private Azure DB</h1><div class=post-meta><span>2023-05-22</span><span> (updated: 2024-01-01)</span><div class=post-tags><a class=post-tag href=/tags/aks>aks</a><a class=post-tag href=/tags/aws>aws</a><a class=post-tag href=/tags/azure>azure</a><a class=post-tag href=/tags/bastion>bastion</a><a class=post-tag href=/tags/cloud-sql-proxy>cloud‑sql‑proxy</a><a class=post-tag href=/tags/database>database</a><a class=post-tag href=/tags/eks>eks</a><a class=post-tag href=/tags/k8s>k8s</a><a class=post-tag href=/tags/kubectl-plugin-socks5-proxy>kubectl‑plugin‑socks5‑proxy</a><a class=post-tag href=/tags/proxy>proxy</a><a class=post-tag href=/tags/socat>socat</a><a class=post-tag href=/tags/socks>socks</a></div></div></div><div class=post-content><p>I ran into a roadblock recently where I wanted to be able to conveniently connect to a managed postgres database within Azure that was not running on public subnets. And by conveniently, I mean that I’d rather not have to spin up an ephemeral virtual machine running in the same network and proxy the connection, and I’d like to use a local client (preferably with a GUI). After several web searches, it became evident that Azure does not readily provide much tooling to support this.</p><h2 id=go-public>Go Public?<a href=#go-public class=post-heading__anchor aria-hidden=true>#</a></h2><p>Should the database be migrated to public subnets? Ideally not, since it is good practice to host internal infrastructure in restricted subnets.</p><h2 id=how-do-others-handle-this>How do others handle this?<a href=#how-do-others-handle-this class=post-heading__anchor aria-hidden=true>#</a></h2><p>With GCP, connecting to a private db instance from any machine can be achieved with <a href=https://github.com/GoogleCloudPlatform/cloud-sql-proxy class=link--external target=_blank rel=noreferrer>cloud-sql-proxy</a>. This works by proxying requests from your machine to the SQL database instance in the cloud, while the authentication is handled by GCP’s IAM.</p><p>So what about Azure? Is there any solution that is as elegant as cloud-sql-proxy?</p><h2 id=a-bastion>A Bastion<a href=#a-bastion class=post-heading__anchor aria-hidden=true>#</a></h2><p>Similar to what <a href=https://aws.amazon.com/blogs/database/securely-connect-to-an-amazon-rds-or-amazon-ec2-database-instance-remotely-with-your-preferred-gui/ class=link--external target=_blank rel=noreferrer>AWS has recommended</a>, perhaps a bastion is the way forward?</p><p>Azure has a fully-managed service called <a href=https://azure.microsoft.com/en-ca/products/azure-bastion class=link--external target=_blank rel=noreferrer>Azure Bastion</a> that provides secure access to virtual machines that do not have public IPs. This looks interesting, but unfortunately it <a href=https://azure.microsoft.com/en-ca/pricing/details/azure-bastion/#pricing class=link--external target=_blank rel=noreferrer>costs money</a> and requires an additional virtual machine.</p><p>Because this adds cost (and complexity), it does not seem like a desirable option in its current state. If it provided a more seamless connection to the database, it would be more appealing.</p><h2 id=socks>SOCKS<a href=#socks class=post-heading__anchor aria-hidden=true>#</a></h2><blockquote><p><strong>2023-12-13:</strong>
|
|
An alternative to using a socks proxy is <a href=http://www.dest-unreach.org/socat/ class=link--external target=_blank rel=noreferrer>socat</a>. This would allow you to relay tcp connections to a pod running in k8s, and then port-forward them to your localhost.
|
|
If this sounds more appealing, install <a href=https://github.com/antitree/krew-net-forward/tree/master class=link--external target=_blank rel=noreferrer>krew-net-forward</a> and then run “kubectl net-forward -i mydb.postgres.database.azure.com -p 5432 -l 5432” to access the database through “localhost:5432”</p></blockquote><p><a href=https://en.wikipedia.org/wiki/SOCKS class=link--external target=_blank rel=noreferrer>SOCKS</a> is a protocol that enables a way to proxy connections by exchanging network packets between the client and the server. There are many implementations and many readily available container images that can run a SOCKS server.</p><p>It’s possible to use this sort of proxy to connect to a private DB, but is it any simpler than using a virtual machine as a jumphost? It wasn’t until I stumbled upon <a href=https://github.com/yokawasa/kubectl-plugin-socks5-proxy class=link--external target=_blank rel=noreferrer>kubectl-plugin-socks5-proxy</a> that I was convinced that using SOCKS could be made simple.</p><p>So how does it work? By installing the kubectl plugin and then running <code>kubectl socks5-proxy</code>, a SOCKS proxy server is spun up in a pod and then opens up port-forwarding session using kubectl.</p><p>As you can see below, this k8s plugin is wrapped up nicely:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-console data-lang=console><span style=display:flex><span>$ kubectl socks5-proxy
|
|
</span></span><span style=display:flex><span>using: namespace=default
|
|
</span></span><span style=display:flex><span>using: port=1080
|
|
</span></span><span style=display:flex><span>using: name=davegallant-proxy
|
|
</span></span><span style=display:flex><span>using: image=serjs/go-socks5-proxy
|
|
</span></span><span style=display:flex><span>Creating SOCKS5 Proxy (Pod)...
|
|
</span></span><span style=display:flex><span>pod/davegallant-proxy created
|
|
</span></span></code></pre></div><p>With the above proxy connection open, it is possible to access both the DNS and private IPs accessible within the k8s cluster. In this case, I am able to access the private database, since there is network connectivity between the k8s cluster and the database.</p><h2 id=caveats-and-conclusion>Caveats and Conclusion<a href=#caveats-and-conclusion class=post-heading__anchor aria-hidden=true>#</a></h2><p>The above outlined solution makes some assumptions:</p><ul><li>there is a k8s cluster</li><li>the k8s cluster has network connectivity to the desired private database</li></ul><p>If these stars align, than this solution might work as a stopgap for accessing a private Azure DB (and I’m assuming this could work similarly on AWS).</p><p>It would be nice if Azure provided tooling similar to cloud-sql-proxy, so that using private databases would be more of a convenient experience.</p><p>One other thing to note is that some clients (such as <a href=https://dbeaver.io/ class=link--external target=_blank rel=noreferrer>dbeaver</a>) <a href=https://github.com/dbeaver/dbeaver/issues/872 class=link--external target=_blank rel=noreferrer>do not provide DNS resolution over SOCKS</a>. So in this case, you won’t be able to use DNS as if you were inside the cluster, but instead have to rely on knowing private ip addresses.</p></div><script type=text/javascript src=https://storage.ko-fi.com/cdn/widget/Widget_2.js></script><script type=text/javascript>kofiwidget2.init("Buy me a coffee","#458588","F1F2S4LWI"),kofiwidget2.draw()</script><section id=comments class=comments><div class='container sep-before'><div class=comments><script>var getTheme=window.localStorage&&window.localStorage.getItem("theme"),getTheme=getTheme??"dark";let theme=getTheme==="dark"?"gruvbox-dark":"github-light",s=document.createElement("script");s.src="https://utteranc.es/client.js",s.setAttribute("repo","davegallant/davegallant.github.io"),s.setAttribute("issue-term","pathname"),s.setAttribute("theme",theme),s.setAttribute("crossorigin","anonymous"),s.setAttribute("async",""),document.querySelector("div.comments").innerHTML="",document.querySelector("div.comments").appendChild(s)</script></div></div></section></article></div><div class=sidebar><aside class=bio><div class="jr__item jr-basics__item"><div class=jr-basics__name>Dave Gallant</div><div class=jr-basics__email>me@davegallant.ca</div><div class=jr-basics__summary>👋 I'm a software tinkerer with a passion for infra, security and self-hosting.</div><hr><div class="jr-basics__profile jr-basics__profile--row"><a href=https://linktr.ee/davegallant target=_blank rel="noreferrer me"><div class=jr-basics__profile-item><div class=jr-basics__profile-icon><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Linktree</title><path d="m13.73635 5.85251 4.00467-4.11665 2.3248 2.3808-4.20064 4.00466h5.9085v3.30473h-5.9365l4.22865 4.10766-2.3248 2.3338L12.0005 12.099l-5.74052 5.76852-2.3248-2.3248 4.22864-4.10766h-5.9375V8.12132h5.9085L3.93417 4.11666l2.3248-2.3808 4.00468 4.11665V0h3.4727zm-3.4727 10.30614h3.4727V24h-3.4727z"/></svg></div></div></a><a href=https://github.com/davegallant target=_blank rel="noreferrer me"><div class=jr-basics__profile-item><div class=jr-basics__profile-icon><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63.0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577.0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93.0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176.0.0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22.0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22.0 1.606-.015 2.896-.015 3.286.0.315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></div></div></a><a href=https://mastodon.social/@davegallant target=_blank rel="noreferrer me"><div class=jr-basics__profile-item><div class=jr-basics__profile-icon><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Mastodon</title><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792.0 11.813.0h-.03c-3.98.0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057.0 00.023-.043v-1.809a.052.052.0 00-.02-.041.053.053.0 00-.046-.01 20.282 20.282.0 01-4.709.545c-2.73.0-3.463-1.284-3.674-1.818a5.593 5.593.0 01-.319-1.433.053.053.0 01.066-.054c1.517.363 3.072.546 4.632.546.376.0.75.0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23.0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112.0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311.0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13.0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg></div></div></a><a href=https://www.linkedin.com/in/dave-gallant target=_blank rel="noreferrer me"><div class=jr-basics__profile-item><div class=jr-basics__profile-icon><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>LinkedIn</title><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853.0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601.0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144.0-2.063-.926-2.063-2.065.0-1.138.92-2.063 2.063-2.063 1.14.0 2.064.925 2.064 2.063.0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225.0H1.771C.792.0.0.774.0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2.0 22.222.0h.003z"/></svg></div></div></a></div></div></aside></div></main><footer><div class=copyright>Dave Gallant</div></footer><script src=/js/main.4be06c129d6a89e60a661c6ac8c8e0434d58fb0fa2f685f85e2c306aca62adc5e77e7c63cb1c8a2cc5794ea42927281cf868514bcdce21ddf23dc3520e6743e7.js integrity="sha512-S+BsEp1qieYKZhxqyMjgQ01Y+w+i9oX4XiwwaspircXnfnxjyxyKLMV5TqQpJygc+GhRS83OId3yPcNSDmdD5w=="></script><script src=/js/flexsearch.6008453bea2c3113a5612f78b88f04db99ba8fb4ce62b8ee2facd2970062f3f2cf949bebc2b610a40366d44598c9a453b7c6d502e4089844ce707f118ae649db.js integrity="sha512-YAhFO+osMROlYS94uI8E25m6j7TOYrjuL6zSlwBi8/LPlJvrwrYQpANm1EWYyaRTt8bVAuQImETOcH8RiuZJ2w=="></script></div></body></html> |