Merge pull request #246 from desimone/docs/v0-2-0-face-lift

docs: refresh docs ui
This commit is contained in:
Bobby DeSimone 2019-08-05 15:59:40 -07:00 committed by GitHub
commit e1c00b1646
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
214 changed files with 4023 additions and 930 deletions

View file

@ -1,5 +0,0 @@
{
"projects": {
"default": "pomerium-21f57"
}
}

5
.gitignore vendored
View file

@ -38,11 +38,6 @@ _testmain.go
# Without this, the *.[568vq] above ignores this folder.
!**/graphrbac/1.6
# Ruby
website/vendor
website/build
website/tmp
.DS_Store
.idea

View file

@ -1,27 +1,41 @@
<a href="https://pomerium.io" title="Pomerium is a zero trust, context and identity aware access proxy."><img src="./docs/.vuepress/public/logo-long.svg" height="70" alt="pomerium logo"></a>
<a href="https://pomerium.io" title="Pomerium is a zero trust, context and identity aware access proxy."><img src="https://www.pomerium.io/logo-long.svg" height="70" alt="pomerium logo"></a>
[![pomerium chat](https://img.shields.io/badge/chat-on%20slack-blue.svg?style=flat&logo=slack)](http://slack.pomerium.io)
[![Travis CI](https://travis-ci.org/pomerium/pomerium.svg?branch=master)](https://travis-ci.org/pomerium/pomerium) [![Go Report Card](https://goreportcard.com/badge/github.com/pomerium/pomerium)](https://goreportcard.com/report/github.com/pomerium/pomerium) [![GoDoc](https://godoc.org/github.com/pomerium/pomerium?status.svg)][godocs] [![LICENSE](https://img.shields.io/github/license/pomerium/pomerium.svg)](https://github.com/pomerium/pomerium/blob/master/LICENSE) [![codecov](https://img.shields.io/codecov/c/github/pomerium/pomerium.svg?style=flat)](https://codecov.io/gh/pomerium/pomerium)
[![Travis CI](https://travis-ci.org/pomerium/pomerium.svg?branch=master)](https://travis-ci.org/pomerium/pomerium) [![Go Report Card](https://goreportcard.com/badge/github.com/pomerium/pomerium)](https://goreportcard.com/report/github.com/pomerium/pomerium) [![GoDoc](https://godoc.org/github.com/pomerium/pomerium?status.svg)][godocs] [![LICENSE](https://img.shields.io/github/license/pomerium/pomerium.svg)](https://github.com/pomerium/pomerium/blob/master/LICENSE) [![codecov](https://img.shields.io/codecov/c/github/pomerium/pomerium.svg?style=flat)](https://codecov.io/gh/pomerium/pomerium) ![Docker Pulls](https://img.shields.io/docker/pulls/pomerium/pomerium)
Pomerium is a tool for managing secure access to internal applications and resources.
Pomerium is an identity-aware proxy that enables secure access to internal applications. Pomerium provides a standardized interface to add access control to applications regardless of whether the application itself has authorization or authentication baked-in. Pomerium gateways both internal and external requests, and can be used in situations where you'd typically reach for a VPN.
Use Pomerium to:
Pomerium can be used to:
- provide a single-sign-on gateway to internal applications.
- enforce dynamic access policy based on **context**, **identity**, and **device state**.
- provide a **single-sign-on gateway** to internal applications.
- enforce **dynamic access policy** based on **context**, **identity**, and **device state**.
- aggregate access logs and telemetry data.
- an alternative to a VPN.
- a **VPN alternative**.
Check out [awesome-zero-trust] to learn more about some of the problems Pomerium attempts to address.
## Architecture
<img alt="pomerium architecture diagram" src="https://www.pomerium.io/pomerium-diagram.svg" width="100%">
## Demo
To make this a bit more concrete, see the following:
1. An **unauthorized** user authenticating with their corporate single-sign-on provider (in this case Google)
2. The **unauthorized** user being blocked from a protected resource.
3. The **unauthorized** user signing out from their session.
4. An **authorized** user authenticating with their corporate single-sign-on provider.
5. Pomerium delegating and grating access to the requested resource.
6. The **authorized** user inspecting their user details including group membership.
<video autoplay="" loop="" muted="" playsinline="" width="100%" height="600" control=""><source src="https://www.pomerium.io/pomerium-in-action-800-600.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
## Docs
To get started with pomerium, check out our [quick start guide].
For comprehensive docs, and tutorials see our [documentation].
For comprehensive docs, and tutorials see our [documentation] and the [godocs].
[awesome-zero-trust]: https://github.com/pomerium/awesome-zero-trust
[documentation]: https://www.pomerium.io/docs/
[documentation]: https://www.pomerium.io/
[go environment]: https://golang.org/doc/install
[godocs]: https://godoc.org/github.com/pomerium/pomerium
[quick start guide]: https://www.pomerium.io/guide/

View file

@ -1,7 +1,8 @@
// .vuepress/config.js
module.exports = {
title: "Pomerium",
description: "An open source identity-aware access proxy.",
description:
"Pomerium is a beyond-corp inspired, zero trust, open source identity-aware access proxy.",
plugins: {
sitemap: {
hostname: "https://www.pomerium.io"
@ -10,58 +11,99 @@ module.exports = {
ga: "UA-129872447-2"
}
},
extend: "@vuepress/theme-default",
markdown: {
externalLinkSymbol: false
},
themeConfig: {
repo: "pomerium/pomerium",
logo: "/logo-long-civez.png",
editLinks: true,
docsDir: "docs",
editLinkText: "Edit this page on GitHub",
lastUpdated: "Last Updated",
nav: [
{ text: "Documentation", link: "/docs/" },
{ text: "Quick Start", link: "/guide/" },
{ text: "Config Reference", link: "/reference/" },
{
text: "Versions",
items: [
{ text: "v0.0.5", link: "https://v0-0-5.docs.pomerium.io/" },
{ text: "v0.0.4", link: "https://v0-0-4.docs.pomerium.io/" }
]
}
{ text: "Community", link: "/community/" }
// {
// text: "Versions",
// items: [
// { text: "v0.1.0", link: "https://v0-1-0.docs.pomerium.io/" },
// { text: "v0.0.5", link: "https://v0-0-5.docs.pomerium.io/" },
// { text: "v0.0.4", link: "https://v0-0-4.docs.pomerium.io/" }
// ]
// }
],
sidebar: {
"/guide/": guideSidebar("Quick Start"),
"/docs/": docsSidebar("Documentation")
"/docs/": [
{
title: "",
type: "group",
collapsable: false,
sidebarDepth: 0,
children: ["", "background", "releases", "upgrading", "CHANGELOG"]
},
{
title: "Quick Start",
collapsable: false,
path: "/docs/quick-start/",
type: "group",
sidebarDepth: 0,
children: [
"quick-start/",
"quick-start/binary",
"quick-start/helm",
"quick-start/kubernetes",
"quick-start/synology"
]
},
{
title: "Identity Providers",
collapsable: false,
path: "/docs/identity-providers/",
type: "group",
sidebarDepth: 0,
children: [
"identity-providers/",
"identity-providers/azure",
"identity-providers/gitlab",
"identity-providers/google",
"identity-providers/okta",
"identity-providers/one-login"
]
},
{
title: "Reference",
collapsable: true,
path: "/docs/reference/",
type: "group",
collapsable: false,
sidebarDepth: 1,
children: [
"reference/certificates",
"reference/impersonation",
"reference/signed-headers",
"reference/examples",
"reference/reference"
]
}
],
"/community/": [
{
title: "Community",
type: "group",
collapsable: false,
sidebarDepth: 1,
children: [
"",
"contributing",
"developers-guide",
"code-of-conduct",
"security"
]
}
]
}
}
};
function guideSidebar(title) {
return [
{
title,
collapsable: false,
children: ["", "binary", "from-source", "helm", "kubernetes", "synology"]
}
];
}
function docsSidebar(title) {
return [
{
title,
collapsable: false,
children: [
"",
"identity-providers",
"signed-headers",
"certificates",
"examples",
"impersonation",
"programmatic-access",
"upgrading",
"contributing"
]
}
];
}

View file

@ -1,4 +0,0 @@
$accentColor = #6c63ff
$textColor = #2c3e50
$borderColor = #eaecef
$codeBgColor = #282c34

View file

@ -0,0 +1 @@
<svg id="29e4b1a0-13d0-4a0f-9be2-3c3977a6a4ba" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="785" height="753.73" viewBox="0 0 785 753.73"><defs><linearGradient id="b4c3f788-9d51-49bd-96f9-c54f9fd51398" x1="452.8" y1="753.73" x2="452.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient><linearGradient id="778ed1c4-cafd-494e-8a04-dc2b91aee6fd" x1="209.37" y1="339.46" x2="209.37" y2="101.61" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="92c6cb1b-63b4-4a00-ae9b-05b36bfadf3e" x1="32.38" y1="227.8" x2="174.2" y2="227.8" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="3111d6ac-9e05-4a73-9878-bd628330955d" x1="211.6" y1="606.34" x2="211.6" y2="582.89" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="2e076d7a-ee6b-46b6-b939-69cbad87608e" x1="572.28" y1="718" x2="572.28" y2="646.54" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="acad6429-7160-42af-9a00-db2d81d2388a" x1="630.34" y1="245.66" x2="630.34" y2="91.56" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/></defs><title>account</title><rect x="120.6" width="664.4" height="753.73" fill="url(#b4c3f788-9d51-49bd-96f9-c54f9fd51398)"/><rect x="130.65" y="14.52" width="643.19" height="725.82" fill="#fff"/><rect x="65.88" y="134" width="365.14" height="232.26" fill="#6c63ff" opacity="0.2"/><rect y="101.61" width="418.74" height="237.84" fill="url(#778ed1c4-cafd-494e-8a04-dc2b91aee6fd)"/><rect x="4.47" y="136.23" width="408.69" height="197.65" fill="#fff"/><rect x="32.38" y="152.98" width="141.81" height="149.63" fill="url(#92c6cb1b-63b4-4a00-ae9b-05b36bfadf3e)"/><rect x="36.85" y="156.33" width="134" height="142.93" fill="#6c63ff"/><path d="M348.77,285.9c-1-8.49-2.5-17-5.75-24.94s-8.47-15.24-15.74-19.75a18,18,0,0,0-8.7-3c-5.69-.2-10.95,3.64-13.88,8.52a39.74,39.74,0,0,0-2.59,5.27,12.27,12.27,0,0,0-10.18,11.24,31.28,31.28,0,0,0,4.42,59.06v5.33a31.39,31.39,0,0,0-23.45,30.27v14.52h62.53V357.88A31.39,31.39,0,0,0,312,327.61v-5.33A31.28,31.28,0,0,0,335.45,292c0-.07,0-.15,0-.22l3.52,7.83c3.35,7.46,6.78,15.46,5.47,23.54C350.86,312.15,350.33,298.54,348.77,285.9Z" transform="translate(-207.5 -73.13)" fill="#fff"/><rect x="4.47" y="106.08" width="408.69" height="30.15" fill="#6c63ff"/><rect x="225.56" y="198.2" width="155.21" height="10.05" fill="#e0e0e0"/><rect x="225.56" y="240.64" width="155.21" height="10.05" fill="#f5f5f5"/><rect x="224.45" y="219.42" width="91.56" height="10.05" fill="#6c63ff" opacity="0.2"/><rect x="224.45" y="261.85" width="62.53" height="10.05" fill="#69f0ae" opacity="0.2"/><rect x="298.14" y="261.85" width="62.53" height="10.05" fill="#69f0ae"/><rect x="185.36" y="419.86" width="539.34" height="18.98" fill="#e0e0e0"/><rect x="185.36" y="461.17" width="179.78" height="18.98" fill="#e0e0e0"/><rect x="544.92" y="461.17" width="179.78" height="18.98" fill="#69f0ae"/><rect x="185.36" y="502.49" width="539.34" height="18.98" fill="#e0e0e0"/><rect x="185.36" y="543.81" width="539.34" height="18.98" fill="#e0e0e0"/><rect x="183.13" y="582.89" width="56.95" height="23.45" fill="url(#3111d6ac-9e05-4a73-9878-bd628330955d)"/><rect x="185.36" y="585.12" width="53.6" height="18.98" fill="#6c63ff"/><rect x="426.56" y="585.12" width="53.6" height="18.98" fill="#69f0ae"/><rect x="671.1" y="585.12" width="53.6" height="18.98" fill="#69f0ae"/><rect x="413.16" y="646.54" width="318.24" height="71.47" fill="url(#2e076d7a-ee6b-46b6-b939-69cbad87608e)"/><rect x="418.74" y="654.35" width="305.96" height="54.72" fill="#6c63ff"/><rect x="418.74" y="654.35" width="305.96" height="54.72" opacity="0.2"/><rect x="529.29" y="91.56" width="202.11" height="154.1" fill="url(#acad6429-7160-42af-9a00-db2d81d2388a)"/><rect x="537.11" y="101.61" width="187.6" height="134" fill="#6c63ff"/><rect x="537.11" y="101.61" width="187.6" height="134" fill="#fff" opacity="0.2"/></svg>

After

Width:  |  Height:  |  Size: 4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1119.78 126.78"><defs><style>.a{fill:#6e43e8;}.b{fill:#fff;}</style></defs><title>logo-long</title><path class="a" d="M453.81,236.69A29.54,29.54,0,0,1,451,249.52q-5.16,11.11-18.16,15.5a24.7,24.7,0,0,1-7.82,1.57,36.4,36.4,0,0,1-16.75-3.6v-1.57a30.48,30.48,0,0,0,21.13-2.5q8.61-4.38,11.74-14.41a20.53,20.53,0,0,0,.94-5.94v-3.45q-.31-11-5.79-17.45t-15.34-6.5h-18v93.45a10.05,10.05,0,0,0,3.05,7,9.49,9.49,0,0,0,7,2.89h2v1.57H380.71v-1.57h2a9.49,9.49,0,0,0,7-2.89,10.05,10.05,0,0,0,3.05-7v-87.5a10.16,10.16,0,0,0-3.28-6.49,9.61,9.61,0,0,0-6.74-2.59h-1.87l-.16-1.56h40.85q15,0,23.64,8.06T453.81,236.69Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M493.73,226.12a60,60,0,0,1,53.06,0A48.42,48.42,0,0,1,566,243.58a49.2,49.2,0,0,1,0,51,48.34,48.34,0,0,1-19.17,17.46,53.62,53.62,0,0,1-24.73,6.18,64.2,64.2,0,0,1-22.39-3.6,51.55,51.55,0,0,1-16.82-10A46.19,46.19,0,0,1,471.58,289q-4.15-9.24-4.15-21.68a43.65,43.65,0,0,1,7.12-23.71A48.38,48.38,0,0,1,493.73,226.12Zm-2.9,75q11.82,12.45,29.35,12.44a39.06,39.06,0,0,0,29.43-12.44q11.89-12.45,11.89-32t-11.89-32a39.06,39.06,0,0,0-29.43-12.44q-17.53,0-29.35,12.44t-11.82,32Q479,288.66,490.83,301.1Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M713.18,314.48h2.35v1.57H683v-1.41h2.35a5.71,5.71,0,0,0,4.15-1.8,4.94,4.94,0,0,0,1.48-4.3L682.81,244l-29.58,58.23q-4.38,9.56-5.48,16h-1.56l-37.73-72.79-7,63.09a4.92,4.92,0,0,0,1.48,4.22,5.78,5.78,0,0,0,4.31,1.88h2.19v1.41h-29v-1.57h2.35a10.42,10.42,0,0,0,8.77-4.07,9.51,9.51,0,0,0,1.87-4.85l12.21-85.62h1.57l41.32,79.52,39.92-79.52h1.41l12.68,85.93a9.58,9.58,0,0,0,3.6,6.27A11,11,0,0,0,713.18,314.48Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M727.42,314.48a8.89,8.89,0,0,0,6.34-2.42,8.31,8.31,0,0,0,2.74-5.87V231.84a7.45,7.45,0,0,0-2.66-5.8,9.39,9.39,0,0,0-6.42-2.34h-2.19v-1.57h45.71a59.57,59.57,0,0,0,11.27-.94,37,37,0,0,0,6.26-1.56v17.84l-1.72-.16V235a8,8,0,0,0-2.19-5.64,7.42,7.42,0,0,0-5.64-2.35H746.36v39.76h26.77a6,6,0,0,0,4.77-1.88,6.84,6.84,0,0,0,1.65-4.69v-2h1.72v21.92h-1.72v-2q0-4.38-3.29-5.95a11,11,0,0,0-2.66-.62H746.36V311.2h24.58a26.59,26.59,0,0,0,12.13-2.51,21,21,0,0,0,7.51-5.95,53.55,53.55,0,0,0,5.25-9.07h1.56l-8.61,22.38H725.23v-1.57Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M838.4,227h-8.29v79a8.37,8.37,0,0,0,4.38,7.36,9.4,9.4,0,0,0,4.54,1.09h2l.16,1.57H809.13v-1.57h2.19a8.69,8.69,0,0,0,6.18-2.42,7.75,7.75,0,0,0,2.59-5.87V231.84a7.52,7.52,0,0,0-2.59-5.8,8.88,8.88,0,0,0-6.18-2.34h-2.19v-1.57h39.29q14.72,0,23.24,6.73a21.46,21.46,0,0,1,8.54,17.69q0,10.95-6.89,18.31a27,27,0,0,1-17.53,8.46q5,2.19,12.05,12.21-.17,0,5.4,6.88t8.92,10.73c2.24,2.55,4.1,4.51,5.56,5.86a18.76,18.76,0,0,0,13.3,5.48v1.57h-4.85q-12.36,0-19.41-4.85a45.75,45.75,0,0,1-9.7-8.93q-.94-1.25-7.91-11t-7.43-10.41q-8.46-10.49-15.81-10.49v-1.56q18.32,0,22.07-1.88c3.44-1.78,5.79-3.65,7-5.64a25.44,25.44,0,0,0,3.91-13.77q0-9.56-5.63-14.72t-15.18-5.63C845.29,227,842.05,227,838.4,227Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M909.47,314.48a11.42,11.42,0,0,0,7.35-2.5,7.42,7.42,0,0,0,3.13-6V232.15a7.42,7.42,0,0,0-3.13-6,11.65,11.65,0,0,0-7.51-2.5h-2v-1.57h35.37v1.57h-2a11.6,11.6,0,0,0-7.28,2.34,8.05,8.05,0,0,0-3.37,5.8v74.35a7.72,7.72,0,0,0,3.29,5.87,11.65,11.65,0,0,0,7.36,2.42h2l.16,1.57H907.28v-1.57Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M1055.35,223.7H1053a7.22,7.22,0,0,0-6.41,3.28,6.78,6.78,0,0,0-1.1,3.29V283q0,16.29-10.41,25.75t-28.57,9.47q-18.15,0-28.64-9.39t-10.49-25.67V230.43a6.18,6.18,0,0,0-2.19-4.78,7.71,7.71,0,0,0-5.32-2h-2.35v-1.57h29.74v1.57h-2.35a7.73,7.73,0,0,0-5.32,2,6.18,6.18,0,0,0-2.19,4.78V281.3q0,15.18,7.75,23.95t21.91,8.76q14.18,0,22.7-8.29t8.53-22.7V230.43a6.71,6.71,0,0,0-2.27-4.86,7.61,7.61,0,0,0-5.24-1.87h-2.35v-1.57h26.92Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M1194,314.48h2.35v1.57h-32.56v-1.41h2.35a5.69,5.69,0,0,0,4.14-1.8,4.92,4.92,0,0,0,1.49-4.3l-8.14-64.5-29.58,58.23q-4.38,9.56-5.48,16H1127l-37.72-72.79-7,63.09a4.92,4.92,0,0,0,1.48,4.22,5.78,5.78,0,0,0,4.31,1.88h2.19v1.41h-29v-1.57h2.35a10.42,10.42,0,0,0,8.77-4.07,9.51,9.51,0,0,0,1.87-4.85l12.21-85.62h1.57l41.32,79.52,39.92-79.52h1.41l12.67,85.93a9.63,9.63,0,0,0,3.61,6.27A11,11,0,0,0,1194,314.48Z" transform="translate(-76.61 -191.46)"/><path class="b" d="M682.74,341.72" transform="translate(-76.61 -191.46)"/><path class="a" d="M337.76,215.82a24.36,24.36,0,0,0-24.36-24.36H101a24.36,24.36,0,0,0-24.36,24.36V316.19H93.44v-28h0a34.11,34.11,0,1,1,68.21,0h0v28h12.18v-28h0a34.11,34.11,0,1,1,68.21,0h0v28h12.18v-28h0a34.1,34.1,0,1,1,68.2,0h0v28h15.33ZM93.44,241.13a34.11,34.11,0,1,1,68.21,0Zm80.39,0a34.11,34.11,0,1,1,68.21,0Zm80.39,0a34.11,34.11,0,1,1,68.21,0Z" transform="translate(-76.61 -191.46)"/></svg>
<svg viewBox="0 0 1119.78 126.78" xmlns="http://www.w3.org/2000/svg"><g fill="#6e43e8" transform="translate(-76.61 -191.46)"><path d="m453.81 236.69a29.54 29.54 0 0 1 -2.81 12.83q-5.16 11.11-18.16 15.5a24.7 24.7 0 0 1 -7.82 1.57 36.4 36.4 0 0 1 -16.75-3.6v-1.57a30.48 30.48 0 0 0 21.13-2.5q8.61-4.38 11.74-14.41a20.53 20.53 0 0 0 .94-5.94v-3.45q-.31-11-5.79-17.45t-15.34-6.5h-18v93.45a10.05 10.05 0 0 0 3.05 7 9.49 9.49 0 0 0 7 2.89h2v1.57h-34.29v-1.57h2a9.49 9.49 0 0 0 7-2.89 10.05 10.05 0 0 0 3.05-7v-87.5a10.16 10.16 0 0 0 -3.28-6.49 9.61 9.61 0 0 0 -6.74-2.59h-1.87l-.16-1.56h40.85q15 0 23.64 8.06t8.61 22.15z"/><path d="m493.73 226.12a60 60 0 0 1 53.06 0 48.42 48.42 0 0 1 19.21 17.46 49.2 49.2 0 0 1 0 51 48.34 48.34 0 0 1 -19.17 17.46 53.62 53.62 0 0 1 -24.73 6.18 64.2 64.2 0 0 1 -22.39-3.6 51.55 51.55 0 0 1 -16.82-10 46.19 46.19 0 0 1 -11.31-15.62q-4.15-9.24-4.15-21.68a43.65 43.65 0 0 1 7.12-23.71 48.38 48.38 0 0 1 19.18-17.49zm-2.9 75q11.82 12.45 29.35 12.44a39.06 39.06 0 0 0 29.43-12.44q11.89-12.45 11.89-32t-11.89-32a39.06 39.06 0 0 0 -29.43-12.44q-17.53 0-29.35 12.44t-11.82 32q-.01 19.54 11.82 31.98z"/><path d="m713.18 314.48h2.35v1.57h-32.53v-1.41h2.35a5.71 5.71 0 0 0 4.15-1.8 4.94 4.94 0 0 0 1.48-4.3l-8.17-64.54-29.58 58.23q-4.38 9.56-5.48 16h-1.56l-37.73-72.79-7 63.09a4.92 4.92 0 0 0 1.48 4.22 5.78 5.78 0 0 0 4.31 1.88h2.19v1.41h-29v-1.57h2.35a10.42 10.42 0 0 0 8.77-4.07 9.51 9.51 0 0 0 1.87-4.85l12.21-85.62h1.57l41.32 79.52 39.92-79.52h1.41l12.68 85.93a9.58 9.58 0 0 0 3.6 6.27 11 11 0 0 0 7.04 2.35z"/><path d="m727.42 314.48a8.89 8.89 0 0 0 6.34-2.42 8.31 8.31 0 0 0 2.74-5.87v-74.35a7.45 7.45 0 0 0 -2.66-5.8 9.39 9.39 0 0 0 -6.42-2.34h-2.19v-1.57h45.71a59.57 59.57 0 0 0 11.27-.94 37 37 0 0 0 6.26-1.56v17.84l-1.72-.16v-2.31a8 8 0 0 0 -2.19-5.64 7.42 7.42 0 0 0 -5.64-2.35h-32.56v39.76h26.77a6 6 0 0 0 4.77-1.88 6.84 6.84 0 0 0 1.65-4.69v-2h1.72v21.92h-1.72v-2q0-4.38-3.29-5.95a11 11 0 0 0 -2.66-.62h-27.24v39.65h24.58a26.59 26.59 0 0 0 12.13-2.51 21 21 0 0 0 7.51-5.95 53.55 53.55 0 0 0 5.25-9.07h1.56l-8.61 22.38h-63.55v-1.57z"/><path d="m838.4 227h-8.29v79a8.37 8.37 0 0 0 4.38 7.36 9.4 9.4 0 0 0 4.54 1.09h2l.16 1.57h-32.06v-1.57h2.19a8.69 8.69 0 0 0 6.18-2.42 7.75 7.75 0 0 0 2.59-5.87v-74.32a7.52 7.52 0 0 0 -2.59-5.8 8.88 8.88 0 0 0 -6.18-2.34h-2.19v-1.57h39.29q14.72 0 23.24 6.73a21.46 21.46 0 0 1 8.54 17.69q0 10.95-6.89 18.31a27 27 0 0 1 -17.53 8.46q5 2.19 12.05 12.21-.17 0 5.4 6.88t8.92 10.73c2.24 2.55 4.1 4.51 5.56 5.86a18.76 18.76 0 0 0 13.3 5.48v1.57h-4.85q-12.36 0-19.41-4.85a45.75 45.75 0 0 1 -9.7-8.93q-.94-1.25-7.91-11t-7.43-10.41q-8.46-10.49-15.81-10.49v-1.56q18.32 0 22.07-1.88c3.44-1.78 5.79-3.65 7-5.64a25.44 25.44 0 0 0 3.91-13.77q0-9.56-5.63-14.72t-15.18-5.63c-2.78-.17-6.02-.17-9.67-.17z"/><path d="m909.47 314.48a11.42 11.42 0 0 0 7.35-2.5 7.42 7.42 0 0 0 3.13-6v-73.83a7.42 7.42 0 0 0 -3.13-6 11.65 11.65 0 0 0 -7.51-2.5h-2v-1.57h35.37v1.57h-2a11.6 11.6 0 0 0 -7.28 2.34 8.05 8.05 0 0 0 -3.37 5.8v74.35a7.72 7.72 0 0 0 3.29 5.87 11.65 11.65 0 0 0 7.36 2.42h2l.16 1.57h-35.56v-1.57z"/><path d="m1055.35 223.7h-2.35a7.22 7.22 0 0 0 -6.41 3.28 6.78 6.78 0 0 0 -1.1 3.29v52.73q0 16.29-10.41 25.75t-28.57 9.47q-18.15 0-28.64-9.39t-10.49-25.67v-52.73a6.18 6.18 0 0 0 -2.19-4.78 7.71 7.71 0 0 0 -5.32-2h-2.35v-1.57h29.74v1.57h-2.35a7.73 7.73 0 0 0 -5.32 2 6.18 6.18 0 0 0 -2.19 4.78v50.87q0 15.18 7.75 23.95t21.91 8.76q14.18 0 22.7-8.29t8.53-22.7v-52.59a6.71 6.71 0 0 0 -2.27-4.86 7.61 7.61 0 0 0 -5.24-1.87h-2.35v-1.57h26.92z"/><path d="m1194 314.48h2.35v1.57h-32.56v-1.41h2.35a5.69 5.69 0 0 0 4.14-1.8 4.92 4.92 0 0 0 1.49-4.3l-8.14-64.5-29.58 58.23q-4.38 9.56-5.48 16h-1.57l-37.72-72.79-7 63.09a4.92 4.92 0 0 0 1.48 4.22 5.78 5.78 0 0 0 4.31 1.88h2.19v1.41h-29v-1.57h2.35a10.42 10.42 0 0 0 8.77-4.07 9.51 9.51 0 0 0 1.87-4.85l12.21-85.62h1.57l41.32 79.52 39.92-79.52h1.41l12.67 85.93a9.63 9.63 0 0 0 3.61 6.27 11 11 0 0 0 7.04 2.31z"/><path d="m337.76 215.82a24.36 24.36 0 0 0 -24.36-24.36h-212.4a24.36 24.36 0 0 0 -24.36 24.36v100.37h16.8v-28a34.11 34.11 0 1 1 68.21 0v28h12.18v-28a34.11 34.11 0 1 1 68.21 0v28h12.18v-28a34.1 34.1 0 1 1 68.2 0v28h15.33zm-244.32 25.31a34.11 34.11 0 1 1 68.21 0zm80.39 0a34.11 34.11 0 1 1 68.21 0zm80.39 0a34.11 34.11 0 1 1 68.21 0z"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -1 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 597.47 295"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#443266;}</style></defs><title>logo-no-text</title><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-2" d="M90.42,497c0-162.92,133.75-295,298.73-295S687.89,334.08,687.89,497" transform="translate(-90.42 -202)"/><path class="cls-1" d="M599.29,335.48a39.21,39.21,0,0,0-39.2-39.21H218.22A39.21,39.21,0,0,0,179,335.48V497h27.09V451.87h0c0-30.64,24.6-55.47,54.89-55.47s54.84,24.83,54.88,55.47h0V497h19.6V451.87h0c0-30.64,24.59-55.47,54.88-55.47s54.85,24.83,54.89,55.47h0V497h19.6V451.87h0c0-30.64,24.6-55.47,54.89-55.47s54.84,24.83,54.88,55.47h0V497h24.66ZM206.11,376.21c0-30.67,24.57-55.54,54.89-55.54s54.88,24.87,54.88,55.54Zm129.37,0c0-30.67,24.58-55.54,54.89-55.54s54.89,24.87,54.89,55.54Zm129.38,0c0-30.67,24.57-55.54,54.89-55.54s54.88,24.87,54.88,55.54Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/></svg>
<svg viewBox="0 0 597.47 295" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-90.42 -202)"><g fill="#fff"><path d="m365.41 490.89v-.09s0 .06 0 .09z"/><path d="m365.4 490.8v.09s0-.06 0-.09z"/><path d="m365.41 490.89v-.09s0 .06 0 .09z"/><path d="m365.4 490.8v.09s0-.06 0-.09z"/></g><path d="m90.42 497c0-162.92 133.75-295 298.73-295s298.74 132.08 298.74 295" fill="#443266"/><path d="m599.29 335.48a39.21 39.21 0 0 0 -39.2-39.21h-341.87a39.21 39.21 0 0 0 -39.22 39.21v161.52h27.09v-45.13c0-30.64 24.6-55.47 54.89-55.47s54.84 24.83 54.88 55.47v45.13h19.6v-45.13c0-30.64 24.59-55.47 54.88-55.47s54.85 24.83 54.89 55.47v45.13h19.6v-45.13c0-30.64 24.6-55.47 54.89-55.47s54.84 24.83 54.88 55.47v45.13h24.66zm-393.18 40.73c0-30.67 24.57-55.54 54.89-55.54s54.88 24.87 54.88 55.54zm129.37 0c0-30.67 24.58-55.54 54.89-55.54s54.89 24.87 54.89 55.54zm129.38 0c0-30.67 24.57-55.54 54.89-55.54s54.88 24.87 54.88 55.54z" fill="#fff"/><path d="m365.4 490.8v.09s0-.06 0-.09z" fill="#fff"/><path d="m365.41 490.89v-.09s0 .06 0 .09z" fill="#fff"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,16 +0,0 @@
// colors
$accentColor = #6E43E8
$textColor = #24292e
$borderColor = #e8e8fb
$codeBgColor = #282c34
$arrowBgColor = #ccc
// layout
// $navbarHeight = 3.6rem
// $sidebarWidth = 20rem
// $contentWidth = 740px
// // responsive breakpoints
// $MQNarrow = 959px
// $MQMobile = 719px
// $MQMobileNarrow = 419px

View file

@ -0,0 +1,158 @@
<template>
<form
id="search-form"
class="algolia-search-wrapper search-box"
role="search"
>
<input
id="algolia-search-input"
class="search-query"
>
</form>
</template>
<script>
export default {
props: ['options'],
mounted () {
this.initialize(this.options, this.$lang)
},
methods: {
initialize (userOptions, lang) {
Promise.all([
import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'),
import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css')
]).then(([docsearch]) => {
docsearch = docsearch.default
const { algoliaOptions = {}} = userOptions
docsearch(Object.assign(
{},
userOptions,
{
inputSelector: '#algolia-search-input',
// #697 Make docsearch work well at i18n mode.
algoliaOptions: Object.assign({
'facetFilters': [`lang:${lang}`].concat(algoliaOptions.facetFilters || [])
}, algoliaOptions),
handleSelected: (input, event, suggestion) => {
this.$router.push(new URL(suggestion.url).pathname)
}
}
))
})
},
update (options, lang) {
this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">'
this.initialize(options, lang)
}
},
watch: {
$lang (newValue) {
this.update(this.options, newValue)
},
options (newValue) {
this.update(newValue, this.$lang)
}
}
}
</script>
<style lang="stylus">
.algolia-search-wrapper
& > span
vertical-align middle
.algolia-autocomplete
line-height normal
.ds-dropdown-menu
background-color #fff
border 1px solid #999
border-radius 4px
font-size 16px
margin 6px 0 0
padding 4px
text-align left
&:before
border-color #999
[class*=ds-dataset-]
border none
padding 0
.ds-suggestions
margin-top 0
.ds-suggestion
border-bottom 1px solid $borderColor
.algolia-docsearch-suggestion--highlight
color #2c815b
.algolia-docsearch-suggestion
border-color $borderColor
padding 0
.algolia-docsearch-suggestion--category-header
padding 5px 10px
margin-top 0
background $accentColor
color #fff
font-weight 600
.algolia-docsearch-suggestion--highlight
background rgba(255, 255, 255, 0.6)
.algolia-docsearch-suggestion--wrapper
padding 0
.algolia-docsearch-suggestion--title
font-weight 600
margin-bottom 0
color $textColor
.algolia-docsearch-suggestion--subcategory-column
vertical-align top
padding 5px 7px 5px 5px
border-color $borderColor
background #f1f3f5
&:after
display none
.algolia-docsearch-suggestion--subcategory-column-text
color #555
.algolia-docsearch-footer
border-color $borderColor
.ds-cursor .algolia-docsearch-suggestion--content
background-color #e7edf3 !important
color $textColor
@media (min-width: $MQMobile)
.algolia-search-wrapper
.algolia-autocomplete
.algolia-docsearch-suggestion
.algolia-docsearch-suggestion--subcategory-column
float none
width 150px
min-width 150px
display table-cell
.algolia-docsearch-suggestion--content
float none
display table-cell
width 100%
vertical-align top
.ds-dropdown-menu
min-width 515px !important
@media (max-width: $MQMobile)
.algolia-search-wrapper
.ds-dropdown-menu
min-width calc(100vw - 4rem) !important
max-width calc(100vw - 4rem) !important
.algolia-docsearch-suggestion--wrapper
padding 5px 7px 5px 5px !important
.algolia-docsearch-suggestion--subcategory-column
padding 0 !important
background white !important
.algolia-docsearch-suggestion--subcategory-column-text:after
content " > "
font-size 10px
line-height 14.4px
display inline-block
width 5px
margin -3px 3px 0
vertical-align middle
</style>

View file

@ -0,0 +1,179 @@
<template>
<div
class="dropdown-wrapper"
:class="{ open }"
>
<a
class="dropdown-title"
@click="toggle"
>
<span class="title">{{ item.text }}</span>
<span
class="arrow"
:class="open ? 'down' : 'right'"
></span>
</a>
<DropdownTransition>
<ul
class="nav-dropdown"
v-show="open"
>
<li
class="dropdown-item"
:key="subItem.link || index"
v-for="(subItem, index) in item.items"
>
<h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
<ul
class="dropdown-subitem-wrapper"
v-if="subItem.type === 'links'"
>
<li
class="dropdown-subitem"
:key="childSubItem.link"
v-for="childSubItem in subItem.items"
>
<NavLink :item="childSubItem"/>
</li>
</ul>
<NavLink
v-else
:item="subItem"
/>
</li>
</ul>
</DropdownTransition>
</div>
</template>
<script>
import NavLink from '@theme/components/NavLink.vue'
import DropdownTransition from '@theme/components/DropdownTransition.vue'
export default {
components: { NavLink, DropdownTransition },
data () {
return {
open: false
}
},
props: {
item: {
required: true
}
},
methods: {
toggle () {
this.open = !this.open
}
}
}
</script>
<style lang="stylus">
.dropdown-wrapper
cursor pointer
.dropdown-title
display block
&:hover
border-color transparent
.arrow
vertical-align middle
margin-top -1px
margin-left 0.4rem
.nav-dropdown
.dropdown-item
color inherit
line-height 1.7rem
h4
margin 0.45rem 0 0
border-top 1px solid #eee
padding 0.45rem 1.5rem 0 1.25rem
.dropdown-subitem-wrapper
padding 0
list-style none
.dropdown-subitem
font-size 0.9em
a
display block
line-height 1.7rem
position relative
border-bottom none
font-weight 400
margin-bottom 0
padding 0 1.5rem 0 1.25rem
&:hover
color $accentColor
&.router-link-active
color $accentColor
&::after
content ""
width 0
height 0
border-left 5px solid $accentColor
border-top 3px solid transparent
border-bottom 3px solid transparent
position absolute
top calc(50% - 2px)
left 9px
&:first-child h4
margin-top 0
padding-top 0
border-top 0
@media (max-width: $MQMobile)
.dropdown-wrapper
&.open .dropdown-title
margin-bottom 0.5rem
.nav-dropdown
transition height .1s ease-out
overflow hidden
.dropdown-item
h4
border-top 0
margin-top 0
padding-top 0
h4, & > a
font-size 15px
line-height 2rem
.dropdown-subitem
font-size 14px
padding-left 1rem
@media (min-width: $MQMobile)
.dropdown-wrapper
height 1.8rem
&:hover .nav-dropdown
// override the inline style.
display block !important
.dropdown-title .arrow
// make the arrow always down at desktop
border-left 4px solid transparent
border-right 4px solid transparent
border-top 6px solid $arrowBgColor
border-bottom 0
.nav-dropdown
display none
// Avoid height shaked by clicking
height auto !important
box-sizing border-box;
max-height calc(100vh - 2.7rem)
overflow-y auto
position absolute
top 100%
right 0
background-color #fff
padding 0.6rem 0
border 1px solid #ddd
border-bottom-color #ccc
text-align left
border-radius 0.25rem
white-space nowrap
margin 0
</style>

View file

@ -0,0 +1,33 @@
<template>
<transition
name="dropdown"
@enter="setHeight"
@after-enter="unsetHeight"
@before-leave="setHeight"
>
<slot/>
</transition>
</template>
<script>
export default {
name: 'DropdownTransition',
methods: {
setHeight (items) {
// explicitly set height so that it can be transitioned
items.style.height = items.scrollHeight + 'px'
},
unsetHeight (items) {
items.style.height = ''
}
}
}
</script>
<style lang="stylus">
.dropdown-enter, .dropdown-leave-to
height 0 !important
</style>

View file

@ -0,0 +1,330 @@
<template>
<main class="home" aria-labelledby="main-title">
<header class="hero">
<div class="section">
<div class="content">
<h1 v-if="data.heroText !== null" id="main-title">{{ data.heroText || $title || 'Hello' }}</h1>
<p
class="description"
>{{ data.tagline || $description || 'Welcome to your VuePress site' }}</p>
<p class="action" v-if="data.actionText && data.actionLink">
<NavLink class="action-button" :item="actionLink" />
</p>
</div>
<video class="media" autoplay loop muted playsinline>
<source
v-if="data.heroImage"
:src="$withBase(data.heroImage)"
:alt="data.heroAlt "
type="video/mp4"
/>Your browser does not support the video tag.
</video>
</div>
</header>
<div class v-if="data.features && data.features.length">
<div class="features section" v-for="(feature, index) in data.features" :key="index">
<div class="feature">
<img class="media" :src="$withBase(feature.src)" />
<div class="content">
<h2>{{ feature.title }}</h2>
<p>{{ feature.text }}</p>
</div>
</div>
</div>
</div>
<!-- triples -->
<div class v-if="data.triples && data.triples.length">
<div class="triples">
<div class="feature">
<div class="content" v-for="(feature, index) in data.triples" :key="index">
<img class="media" :src="$withBase(feature.src)" />
<h2>{{ feature.title }}</h2>
<p>{{ feature.text }}</p>
</div>
</div>
</div>
</div>
<Content class="theme-default-content custom" />
<div class="footer" v-if="data.footer">{{ data.footer }}</div>
</main>
</template>
<script>
import NavLink from "@theme/components/NavLink.vue";
export default {
components: { NavLink },
computed: {
data() {
return this.$page.frontmatter;
},
actionLink() {
return {
link: this.data.actionLink,
text: this.data.actionText
};
}
}
};
</script>
<style lang="stylus">
.home {
padding: $navbarHeight 0 0;
.hero {
background-color: $accentColor;
padding-top: 96pt - $navbarHeight;
padding-bottom: 96pt;
.section {
padding: 0pt 0;
margin: 0 auto;
max-width: $contentWidth;
justify-content: space-between;
display: flex;
flex-wrap: wrap;
align-items: center;
flex-direction: row;
// flex-flow: column wrap;
.media {
padding: 0 20px;
flex: 1;
max-width: 45%;
align-items: center;
}
.content {
flex: 1;
padding: 0 20px;
h1 {
text-align: left;
margin-left: auto;
margin-right: auto;
font-size: 3.2rem;
font-weight: 600;
border-bottom: none;
padding-bottom: 0;
color: #ffffff;
}
.description {
text-align: left;
margin-left: auto;
margin-right: auto;
font-weight: 300;
color: #ffffff;
font-size: 1.5rem;
line-height: 1.3;
}
.action-button {
display: inline-block;
font-size: 1.2rem;
color: #fff;
background-color: $accentColor;
padding: 0.8rem 1.6rem;
border-radius: 4px;
transition: background-color 0.1s ease;
box-sizing: border-box;
border-bottom: 1px solid darken($accentColor, 10%);
&:hover {
background-color: lighten($accentColor, 10%);
}
}
}
}
}
}
.triples {
display: flex;
flex-wrap: wrap;
margin: 0 auto;
padding: 32pt 0;
background-color: $section-color;
.feature {
padding: 92pt 0;
margin: 0 auto;
max-width: $contentWidth;
display: flex;
justify-content: space-between;
// flex-wrap: wrap;
.content {
display: flex;
flex-direction: column;
align-items: center;
max-width: 100%;
padding-left: 15px;
.media {
max-height: 150px;
}
h2 {
padding: 10pt 0;
display: flex;
font-weight: 500;
border-bottom: none;
padding-bottom: 0;
color: $headerColor;
}
p {
text-align: left;
margin-left: auto;
margin-right: auto;
font-weight: 400;
color: $textColor;
}
}
}
}
.features {
background-color: #ffffff;
padding: 72pt 0;
display: flex;
flex-wrap: wrap;
margin: 0 auto;
&:nth-child(even) {
background-color: $section-color;
border-top: 1px solid darken($section-color, 10%);
border-bottom: 1px solid darken($section-color, 10%);
.feature {
.media {
order: 1;
}
.content {
order: 2;
}
}
}
.feature {
padding: 0pt 0;
margin: 0 auto;
max-width: $contentWidth;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
flex-wrap: wrap;
.media {
padding: 0 20px;
max-height: 400px;
max-width: 45%;
flex: 1;
order: 2;
}
.content {
padding: 0 10px;
max-width: 55%;
order: 1;
flex: 1;
h2 {
font-size: 2.4rem;
font-weight: 600;
border-bottom: none;
padding-bottom: 0;
color: $headerColor;
}
p {
font-size: 1.2rem;
text-align: left;
margin-left: auto;
margin-right: auto;
font-weight: 400;
color: $textColor;
}
}
}
.footer {
padding: 2.5rem;
border-top: 1px solid $borderColor;
text-align: center;
color: lighten($textColor, 25%);
}
}
@media (max-width: $MQMobile) {
.home {
.feature {
flex-direction: column;
}
.hero {
padding-top: 20pt;
.section {
flex-direction: column;
.media {
max-width: 90%;
}
}
}
}
}
@media (max-width: $MQMobileNarrow) {
.home {
// padding-left: 0.5rem;
// padding-right: 0.5rem;
.hero {
padding-top: 20pt;
.section {
flex-direction: column;
.media {
max-width: 90%;
}
}
h1 {
font-size: 2rem;
}
h1, .description, .action {
margin: 1.2rem auto;
}
.description {
font-size: 1.2rem;
}
.action-button {
font-size: 1rem;
padding: 0.6rem 1.2rem;
}
}
.feature {
h2 {
font-size: 1.25rem;
}
}
}
}
</style>

View file

@ -0,0 +1,46 @@
<template>
<router-link class="nav-link" :to="link" v-if="!isExternal(link)" :exact="exact">{{ item.text }}</router-link>
<a
v-else
:href="link"
class="nav-link external"
:target="isMailto(link) || isTel(link) ? null : '_blank'"
:rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'"
>
{{ item.text }}
<OutboundLink />
</a>
</template>
<script>
import { isExternal, isMailto, isTel, ensureExt } from "../util";
export default {
props: {
item: {
required: true
}
},
computed: {
link() {
return ensureExt(this.item.link);
},
exact() {
if (this.$site.locales) {
return Object.keys(this.$site.locales).some(
rootLink => rootLink === this.link
);
}
return this.link === "/";
}
},
methods: {
isExternal,
isMailto,
isTel
}
};
</script>

View file

@ -0,0 +1,150 @@
<template>
<nav class="nav-links" v-if="userLinks.length || repoLink">
<!-- user links -->
<div class="nav-item" v-for="item in userLinks" :key="item.link">
<DropdownLink v-if="item.type === 'links'" :item="item" />
<NavLink v-else :item="item" />
</div>
<!-- repo link -->
<a v-if="repoLink" :href="repoLink" class="repo-link" target="_blank" rel="noopener noreferrer">
{{ repoLabel }}
<OutboundLink />
</a>
</nav>
</template>
<script>
import DropdownLink from "@theme/components/DropdownLink.vue";
import { resolveNavLinkItem } from "../util";
import NavLink from "@theme/components/NavLink.vue";
export default {
components: { NavLink, DropdownLink },
computed: {
userNav() {
return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || [];
},
nav() {
const { locales } = this.$site;
if (locales && Object.keys(locales).length > 1) {
const currentLink = this.$page.path;
const routes = this.$router.options.routes;
const themeLocales = this.$site.themeConfig.locales || {};
const languageDropdown = {
text: this.$themeLocaleConfig.selectText || "Languages",
items: Object.keys(locales).map(path => {
const locale = locales[path];
const text =
(themeLocales[path] && themeLocales[path].label) || locale.lang;
let link;
// Stay on the current page
if (locale.lang === this.$lang) {
link = currentLink;
} else {
// Try to stay on the same page
link = currentLink.replace(this.$localeConfig.path, path);
// fallback to homepage
if (!routes.some(route => route.path === link)) {
link = path;
}
}
return { text, link };
})
};
return [...this.userNav, languageDropdown];
}
return this.userNav;
},
userLinks() {
return (this.nav || []).map(link => {
return Object.assign(resolveNavLinkItem(link), {
items: (link.items || []).map(resolveNavLinkItem)
});
});
},
repoLink() {
const { repo } = this.$site.themeConfig;
if (repo) {
return /^https?:/.test(repo) ? repo : `https://github.com/${repo}`;
}
},
repoLabel() {
if (!this.repoLink) return;
if (this.$site.themeConfig.repoLabel) {
return this.$site.themeConfig.repoLabel;
}
const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0];
const platforms = ["GitHub", "GitLab", "Bitbucket"];
for (let i = 0; i < platforms.length; i++) {
const platform = platforms[i];
if (new RegExp(platform, "i").test(repoHost)) {
return platform;
}
}
return "Source";
}
}
};
</script>
<style lang="stylus">
.nav-links {
display: inline-block;
color: $navbar-text-color;
a {
line-height: 1.4rem;
color: $navbar-text-color;
&:hover, &.router-link-active {
color: $navbar-text-color;
}
}
.nav-item {
position: relative;
display: inline-block;
margin-left: 1.5rem;
line-height: 2rem;
&:first-child {
margin-left: 0;
}
}
.repo-link {
margin-left: 1.5rem;
}
}
@media (max-width: $MQMobile) {
.nav-links {
.nav-item, .repo-link {
margin-left: 0;
}
}
}
@media (min-width: $MQMobile) {
.nav-links a {
&:hover, &.router-link-active {
color: $navbar-text-color;
}
}
.nav-item > a:not(.external) {
&:hover, &.router-link-active {
margin-bottom: -2px;
border-bottom: 2px solid darken($navbar-text-color, 15%);
}
}
}
</style>

View file

@ -0,0 +1,142 @@
<template>
<header class="navbar">
<SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />
<router-link :to="$localePath" class="home-link">
<img
class="logo"
v-if="$site.themeConfig.logo"
:src="$withBase($site.themeConfig.logo)"
:alt="$siteTitle"
/>
</router-link>
<div
class="links"
:style="linksWrapMaxWidth ? {
'max-width': linksWrapMaxWidth + 'px'
} : {}"
>
<NavLinks class="can-hide" />
<AlgoliaSearchBox v-if="isAlgoliaSearch" :options="algolia" />
<SearchBox
v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false"
/>
</div>
</header>
</template>
<script>
import AlgoliaSearchBox from "@AlgoliaSearchBox";
import SearchBox from "@SearchBox";
import SidebarButton from "@theme/components/SidebarButton.vue";
import NavLinks from "@theme/components/NavLinks.vue";
export default {
components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox },
data() {
return {
linksWrapMaxWidth: null
};
},
mounted() {
const MOBILE_DESKTOP_BREAKPOINT = 719; // refer to config.styl
const NAVBAR_VERTICAL_PADDING =
parseInt(css(this.$el, "paddingLeft")) +
parseInt(css(this.$el, "paddingRight"));
const handleLinksWrapWidth = () => {
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
this.linksWrapMaxWidth = null;
} else {
this.linksWrapMaxWidth =
this.$el.offsetWidth -
NAVBAR_VERTICAL_PADDING -
((this.$refs.siteName && this.$refs.siteName.offsetWidth) || 0);
}
};
handleLinksWrapWidth();
window.addEventListener("resize", handleLinksWrapWidth, false);
},
computed: {
algolia() {
return (
this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
);
},
isAlgoliaSearch() {
return this.algolia && this.algolia.apiKey && this.algolia.indexName;
}
}
};
function css(el, property) {
// NOTE: Known bug, will return 'auto' if style value is 'auto'
const win = el.ownerDocument.defaultView;
// null means not to return pseudo styles
return win.getComputedStyle(el, null)[property];
}
</script>
<style lang="stylus">
$navbar-vertical-padding = 0.7rem;
$navbar-horizontal-padding = 1.5rem;
.navbar {
padding: $navbar-vertical-padding $navbar-horizontal-padding;
line-height: $navbarHeight - 1.4rem;
background: $navbar-background;
a, span, img {
display: inline-block;
}
.logo {
height: $navbarHeight - 3.1rem;
margin-right: 0.2rem;
vertical-align: middle;
}
.site-name {
font-size: 1.3rem;
font-weight: 600;
color: $textColor;
position: relative;
}
.links {
// padding-left: 1.5rem;
box-sizing: border-box;
background-color: $navbar-background;
white-space: nowrap;
font-size: 0.95rem;
position: absolute;
right: $navbar-horizontal-padding;
top: $navbar-vertical-padding;
display: flex;
.search-box {
padding-left: 1.5rem;
flex: 0 0 auto;
vertical-align: middle;
}
}
}
@media (max-width: $MQMobile) {
.navbar {
padding-left: 4rem;
.can-hide {
display: none;
}
.links {
padding-left: 1.5rem;
}
}
}
</style>

View file

@ -0,0 +1,241 @@
<template>
<main class="page">
<slot name="top" />
<Content class="theme-default-content" />
<footer class="page-edit">
<div class="edit-link" v-if="editLink">
<a :href="editLink" target="_blank" rel="noopener noreferrer">{{ editLinkText }}</a>
<OutboundLink />
</div>
<div class="last-updated" v-if="lastUpdated">
<span class="prefix">{{ lastUpdatedText }}:</span>
<span class="time">{{ lastUpdated }}</span>
</div>
</footer>
<div class="page-nav" v-if="prev || next">
<p class="inner">
<span v-if="prev" class="prev">
<router-link v-if="prev" class="prev" :to="prev.path">{{ prev.title || prev.path }}</router-link>
</span>
<span v-if="next" class="next">
<router-link v-if="next" :to="next.path">{{ next.title || next.path }}</router-link>
</span>
</p>
</div>
<slot name="bottom" />
</main>
</template>
<script>
import { resolvePage, outboundRE, endingSlashRE } from "../util";
export default {
props: ["sidebarItems"],
computed: {
lastUpdated() {
return this.$page.lastUpdated;
},
lastUpdatedText() {
if (typeof this.$themeLocaleConfig.lastUpdated === "string") {
return this.$themeLocaleConfig.lastUpdated;
}
if (typeof this.$site.themeConfig.lastUpdated === "string") {
return this.$site.themeConfig.lastUpdated;
}
return "Last Updated";
},
prev() {
const prev = this.$page.frontmatter.prev;
if (prev === false) {
return;
} else if (prev) {
return resolvePage(this.$site.pages, prev, this.$route.path);
} else {
return resolvePrev(this.$page, this.sidebarItems);
}
},
next() {
const next = this.$page.frontmatter.next;
if (next === false) {
return;
} else if (next) {
return resolvePage(this.$site.pages, next, this.$route.path);
} else {
return resolveNext(this.$page, this.sidebarItems);
}
},
editLink() {
if (this.$page.frontmatter.editLink === false) {
return;
}
const {
repo,
editLinks,
docsDir = "",
docsBranch = "master",
docsRepo = repo
} = this.$site.themeConfig;
if (docsRepo && editLinks && this.$page.relativePath) {
return this.createEditLink(
repo,
docsRepo,
docsDir,
docsBranch,
this.$page.relativePath
);
}
},
editLinkText() {
return (
this.$themeLocaleConfig.editLinkText ||
this.$site.themeConfig.editLinkText ||
`Edit this page`
);
}
},
methods: {
createEditLink(repo, docsRepo, docsDir, docsBranch, path) {
const bitbucket = /bitbucket.org/;
if (bitbucket.test(repo)) {
const base = outboundRE.test(docsRepo) ? docsRepo : repo;
return (
base.replace(endingSlashRE, "") +
`/src` +
`/${docsBranch}/` +
(docsDir ? docsDir.replace(endingSlashRE, "") + "/" : "") +
path +
`?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
);
}
const base = outboundRE.test(docsRepo)
? docsRepo
: `https://github.com/${docsRepo}`;
return (
base.replace(endingSlashRE, "") +
`/edit` +
`/${docsBranch}/` +
(docsDir ? docsDir.replace(endingSlashRE, "") + "/" : "") +
path
);
}
}
};
function resolvePrev(page, items) {
return find(page, items, -1);
}
function resolveNext(page, items) {
return find(page, items, 1);
}
function find(page, items, offset) {
const res = [];
flatten(items, res);
for (let i = 0; i < res.length; i++) {
const cur = res[i];
if (cur.type === "page" && cur.path === decodeURIComponent(page.path)) {
return res[i + offset];
}
}
}
function flatten(items, res) {
for (let i = 0, l = items.length; i < l; i++) {
if (items[i].type === "group") {
flatten(items[i].children || [], res);
} else {
res.push(items[i]);
}
}
}
</script>
<style lang="stylus">
@require '../styles/wrapper.styl';
.page {
padding-bottom: 2rem;
display: block;
background: rgba(255, 255, 255, 0.8);
}
.page-edit {
@extend $wrapper;
padding-top: 1rem;
padding-bottom: 1rem;
overflow: auto;
.edit-link {
display: inline-block;
a {
color: lighten($textColor, 25%);
margin-right: 0.25rem;
}
}
.last-updated {
float: right;
font-size: 0.9em;
.prefix {
font-weight: 500;
color: lighten($textColor, 25%);
}
.time {
font-weight: 400;
color: #aaa;
}
}
}
.page-nav {
@extend $wrapper;
padding-top: 1rem;
padding-bottom: 0;
.inner {
min-height: 2rem;
margin-top: 0;
border-top: 1px solid $borderColor;
padding-top: 1rem;
overflow: auto; // clear float
}
.next {
float: right;
}
}
@media (max-width: $MQMobile) {
.page-edit {
.edit-link {
margin-bottom: 0.5rem;
}
.last-updated {
font-size: 0.8em;
float: none;
text-align: left;
}
}
}
</style>

View file

@ -0,0 +1,92 @@
<template>
<aside class="sidebar">
<NavLinks />
<slot name="top" />
<SidebarLinks :depth="0" :items="items" />
<slot name="bottom" />
</aside>
</template>
<script>
import SidebarLinks from "@theme/components/SidebarLinks.vue";
import NavLinks from "@theme/components/NavLinks.vue";
export default {
name: "Sidebar",
components: { SidebarLinks, NavLinks },
props: ["items"]
};
</script>
<style lang="stylus">
.sidebar {
background-color: $sidebarColor;
ul {
padding: 0;
margin: 0;
list-style-type: none;
}
a {
display: inline-block;
}
.nav-links {
display: none;
border-bottom: 1px solid $borderColor;
padding: 0.5rem 0 0.75rem 0;
a {
font-weight: 600;
}
.nav-item, .repo-link {
display: block;
line-height: 1.15rem;
font-size: 1.1em;
padding: 0.5rem 0 0.5rem 1.5rem;
}
}
& > .sidebar-links {
padding: 1.5rem 0;
& > li > a.sidebar-link {
font-size: 1.1em;
line-height: 1.3;
font-weight: bold;
}
& > li:not(:first-child) {
margin-top: 0.75rem;
}
}
}
@media (max-width: $MQMobileNarrow) {
.sidebar {
background-color: #6E43E8;
}
}
@media (max-width: $MQMobile) {
.sidebar {
background-color: #6E43E8;
.nav-links {
display: block;
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after {
top: calc(1rem - 2px);
}
}
& > .sidebar-links {
padding: 1rem 0;
}
}
}
</style>

View file

@ -0,0 +1,42 @@
<template>
<div class="sidebar-button" @click="$emit('toggle-sidebar')">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
viewBox="0 0 448 512"
>
<path
fill="currentColor"
d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
class
/>
</svg>
</div>
</template>
<style lang="stylus">
.sidebar-button {
cursor: pointer;
display: none;
width: 1.25rem;
height: 1.25rem;
position: absolute;
padding: 0.6rem;
top: 0.6rem;
left: 1rem;
.icon {
display: block;
width: 1.25rem;
height: 1.25rem;
}
}
@media (max-width: $MQMobile) {
.sidebar-button {
display: block;
}
}
</style>

View file

@ -0,0 +1,136 @@
<template>
<section
class="sidebar-group"
:class="[
{
collapsable,
'is-sub-group': depth !== 0
},
`depth-${depth}`
]"
>
<router-link
v-if="item.path"
class="sidebar-heading clickable"
:class="{
open,
'active': isActive($route, item.path)
}"
:to="item.path"
@click.native="$emit('toggle')"
>
<span>{{ item.title }}</span>
<span class="arrow" v-if="collapsable" :class="open ? 'down' : 'right'"></span>
</router-link>
<p v-else class="sidebar-heading" :class="{ open }" @click="$emit('toggle')">
<span>{{ item.title }}</span>
<span class="arrow" v-if="collapsable" :class="open ? 'down' : 'right'"></span>
</p>
<DropdownTransition>
<SidebarLinks
class="sidebar-group-items"
:items="item.children"
v-if="open || !collapsable"
:sidebarDepth="item.sidebarDepth"
:depth="depth + 1"
/>
</DropdownTransition>
</section>
</template>
<script>
import { isActive } from "../util";
import DropdownTransition from "@theme/components/DropdownTransition.vue";
export default {
name: "SidebarGroup",
props: ["item", "open", "collapsable", "depth"],
components: { DropdownTransition },
// ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
beforeCreate() {
this.$options.components.SidebarLinks = require("./SidebarLinks.vue").default;
},
methods: { isActive }
};
</script>
<style lang="stylus">
.sidebar-group {
.sidebar-group {
padding-left: 0.5em;
}
&:not(.collapsable) {
.sidebar-heading:not(.clickable) {
cursor: auto;
color: inherit;
}
}
// refine styles of nested sidebar groups
&.is-sub-group {
padding-left: 0;
& > .sidebar-heading {
font-size: 0.95em;
line-height: 1.4;
font-weight: normal;
padding-left: 2rem;
&:not(.clickable) {
opacity: 0.5;
}
}
& > .sidebar-group-items {
padding-left: 1rem;
& > li > .sidebar-link {
font-size: 0.95em;
border-left: none;
}
}
}
&.depth-2 {
& > .sidebar-heading {
border-left: none;
}
}
}
.sidebar-heading {
color: lighten($textColor, 50%);
transition: color 0.15s ease;
cursor: pointer;
font-size: 1em;
font-weight: 600;
text-transform: uppercase;
// text-transform uppercase
padding: 0.35rem 1.5rem 0.35rem 1.25rem;
width: 100%;
box-sizing: border-box;
margin: 0;
border-left: 0.25rem solid transparent;
.arrow {
position: relative;
top: -0.12em;
left: 0.5em;
}
&.clickable {
&:hover {
color: $accentColor;
}
}
}
.sidebar-group-items {
transition: height 0.1s ease-out;
font-size: 0.95em;
overflow: hidden;
}
</style>

View file

@ -0,0 +1,153 @@
<script>
import { isActive, hashRE, groupHeaders } from "../util";
export default {
functional: true,
props: ["item", "sidebarDepth"],
render(
h,
{
parent: { $page, $site, $route, $themeConfig, $themeLocaleConfig },
props: { item, sidebarDepth }
}
) {
// use custom active class matching logic
// due to edge case of paths ending with / + hash
const selfActive = isActive($route, item.path);
// for sidebar: auto pages, a hash link should be active if one of its child
// matches
const active =
item.type === "auto"
? selfActive ||
item.children.some(c =>
isActive($route, item.basePath + "#" + c.slug)
)
: selfActive;
const link =
item.type === "external"
? renderExternal(h, item.path, item.title || item.path)
: renderLink(h, item.path, item.title || item.path, active);
const configDepth =
$page.frontmatter.sidebarDepth ||
sidebarDepth ||
$themeLocaleConfig.sidebarDepth ||
$themeConfig.sidebarDepth;
const maxDepth = configDepth == null ? 1 : configDepth;
const displayAllHeaders =
$themeLocaleConfig.displayAllHeaders || $themeConfig.displayAllHeaders;
if (item.type === "auto") {
return [
link,
renderChildren(h, item.children, item.basePath, $route, maxDepth)
];
} else if (
(active || displayAllHeaders) &&
item.headers &&
!hashRE.test(item.path)
) {
const children = groupHeaders(item.headers);
return [link, renderChildren(h, children, item.path, $route, maxDepth)];
} else {
return link;
}
}
};
function renderLink(h, to, text, active) {
return h(
"router-link",
{
props: {
to,
activeClass: "",
exactActiveClass: ""
},
class: {
active,
"sidebar-link": true
}
},
text
);
}
function renderChildren(h, children, path, route, maxDepth, depth = 1) {
if (!children || depth > maxDepth) return null;
return h(
"ul",
{ class: "sidebar-sub-headers" },
children.map(c => {
const active = isActive(route, path + "#" + c.slug);
return h("li", { class: "sidebar-sub-header" }, [
renderLink(h, path + "#" + c.slug, c.title, active),
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
]);
})
);
}
function renderExternal(h, to, text) {
return h(
"a",
{
attrs: {
href: to,
target: "_blank",
rel: "noopener noreferrer"
},
class: {
"sidebar-link": true
}
},
[text, h("OutboundLink")]
);
}
</script>
<style lang="stylus">
.sidebar .sidebar-sub-headers {
padding-left: 1rem;
font-size: 0.95em;
}
a.sidebar-link {
// font-size: 0.95em;
font-weight: 400;
display: inline-block;
color: $textColor;
border-left: 0.25rem solid transparent;
padding: 0.35rem 1rem 0.35rem 1.25rem;
line-height: 1.3;
width: 100%;
box-sizing: border-box;
&:hover {
color: $accentColor;
}
&.active {
font-weight: 600;
color: $accentColor;
}
.sidebar-group & {
padding-left: 2rem;
}
.sidebar-sub-headers & {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
border-left: none;
&.active {
font-weight: 500;
}
}
}
</style>

View file

@ -0,0 +1,79 @@
<template>
<ul class="sidebar-links" v-if="items.length">
<li v-for="(item, i) in items" :key="i">
<SidebarGroup
v-if="item.type === 'group'"
:item="item"
:open="i === openGroupIndex"
:collapsable="item.collapsable || item.collapsible"
:depth="depth"
@toggle="toggleGroup(i)"
/>
<SidebarLink v-else :sidebarDepth="sidebarDepth" :item="item" />
</li>
</ul>
</template>
<script>
import SidebarGroup from "@theme/components/SidebarGroup.vue";
import SidebarLink from "@theme/components/SidebarLink.vue";
import { isActive } from "../util";
export default {
name: "SidebarLinks",
components: { SidebarGroup, SidebarLink },
props: [
"items",
"depth", // depth of current sidebar links
"sidebarDepth" // depth of headers to be extracted
],
data() {
return {
openGroupIndex: 0
};
},
created() {
this.refreshIndex();
},
watch: {
$route() {
this.refreshIndex();
}
},
methods: {
refreshIndex() {
const index = resolveOpenGroupIndex(this.$route, this.items);
if (index > -1) {
this.openGroupIndex = index;
}
},
toggleGroup(index) {
this.openGroupIndex = index === this.openGroupIndex ? -1 : index;
},
isActive(page) {
return isActive(this.$route, page.regularPath);
}
}
};
function resolveOpenGroupIndex(route, items) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (
item.type === "group" &&
item.children.some(c => c.type === "page" && isActive(route, c.path))
) {
return i;
}
}
return -1;
}
</script>

View file

@ -0,0 +1,57 @@
<script>
export default {
functional: true,
props: {
type: {
type: String,
default: "tip"
},
text: String,
vertical: {
type: String,
default: "top"
}
},
render(h, { props, slots }) {
return h(
"span",
{
class: ["badge", props.type],
style: {
verticalAlign: props.vertical
}
},
props.text || slots().default
);
}
};
</script>
<style lang="stylus" scoped>
.badge {
display: inline-block;
font-size: 14px;
height: 18px;
line-height: 18px;
border-radius: 3px;
padding: 0 6px;
color: white;
background-color: #42b983;
&.tip, &.green {
background-color: #42b983;
}
&.error {
background-color: #DA5961; // #f66
}
&.warning, &.warn, &.yellow {
background-color: darken(#ffe564, 35%);
}
& + & {
margin-left: 5px;
}
}
</style>

View file

@ -0,0 +1,52 @@
const path = require("path");
// Theme API.
module.exports = (options, ctx) => ({
alias() {
const { themeConfig, siteConfig } = ctx;
// resolve algolia
const isAlgoliaSearch =
themeConfig.algolia ||
Object.keys((siteConfig.locales && themeConfig.locales) || {}).some(
base => themeConfig.locales[base].algolia
);
return {
"@AlgoliaSearchBox": isAlgoliaSearch
? path.resolve(__dirname, "components/AlgoliaSearchBox.vue")
: path.resolve(__dirname, "noopModule.js")
};
},
plugins: [
["@vuepress/active-header-links", options.activeHeaderLinks],
"@vuepress/search",
"@vuepress/plugin-nprogress",
[
"container",
{
type: "tip",
defaultTitle: {
"/zh/": "提示"
}
}
],
[
"container",
{
type: "warning",
defaultTitle: {
"/zh/": "注意"
}
}
],
[
"container",
{
type: "danger",
defaultTitle: {
"/zh/": "警告"
}
}
]
]
});

View file

@ -0,0 +1,27 @@
<template>
<div class="theme-container">
<div class="theme-default-content">
<h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote>
<router-link to="/">Take me home.</router-link>
</div>
</div>
</template>
<script>
const msgs = [
`There's nothing here.`,
`How did we get here?`,
`That's a Four-Oh-Four.`,
`Looks like we've got some broken links.`
];
export default {
methods: {
getMsg() {
return msgs[Math.floor(Math.random() * msgs.length)];
}
}
};
</script>

View file

@ -0,0 +1,121 @@
<template>
<div
class="theme-container"
:class="pageClasses"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<Navbar v-if="shouldShowNavbar" @toggle-sidebar="toggleSidebar" />
<div class="sidebar-mask" @click="toggleSidebar(false)"></div>
<Sidebar :items="sidebarItems" @toggle-sidebar="toggleSidebar">
<slot name="sidebar-top" slot="top" />
<slot name="sidebar-bottom" slot="bottom" />
</Sidebar>
<Home v-if="$page.frontmatter.home" />
<Page v-else :sidebar-items="sidebarItems">
<slot name="page-top" slot="top" />
<slot name="page-bottom" slot="bottom" />
</Page>
</div>
</template>
<script>
import Home from "@theme/components/Home.vue";
import Navbar from "@theme/components/Navbar.vue";
import Page from "@theme/components/Page.vue";
import Sidebar from "@theme/components/Sidebar.vue";
import { resolveSidebarItems } from "../util";
export default {
components: { Home, Page, Sidebar, Navbar },
data() {
return {
isSidebarOpen: false
};
},
computed: {
shouldShowNavbar() {
const { themeConfig } = this.$site;
const { frontmatter } = this.$page;
if (frontmatter.navbar === false || themeConfig.navbar === false) {
return false;
}
return (
this.$title ||
themeConfig.logo ||
themeConfig.repo ||
themeConfig.nav ||
this.$themeLocaleConfig.nav
);
},
shouldShowSidebar() {
const { frontmatter } = this.$page;
return (
!frontmatter.home &&
frontmatter.sidebar !== false &&
this.sidebarItems.length
);
},
sidebarItems() {
return resolveSidebarItems(
this.$page,
this.$page.regularPath,
this.$site,
this.$localePath
);
},
pageClasses() {
const userPageClass = this.$page.frontmatter.pageClass;
return [
{
"no-navbar": !this.shouldShowNavbar,
"sidebar-open": this.isSidebarOpen,
"no-sidebar": !this.shouldShowSidebar
},
userPageClass
];
}
},
mounted() {
this.$router.afterEach(() => {
this.isSidebarOpen = false;
});
},
methods: {
toggleSidebar(to) {
this.isSidebarOpen = typeof to === "boolean" ? to : !this.isSidebarOpen;
},
// side swipe
onTouchStart(e) {
this.touchStart = {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY
};
},
onTouchEnd(e) {
const dx = e.changedTouches[0].clientX - this.touchStart.x;
const dy = e.changedTouches[0].clientY - this.touchStart.y;
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {
if (dx > 0 && this.touchStart.x <= 80) {
this.toggleSidebar(true);
} else {
this.toggleSidebar(false);
}
}
}
}
};
</script>

View file

@ -0,0 +1 @@
export default {}

View file

@ -0,0 +1,41 @@
{
"name": "@vuepress/theme-default",
"version": "1.0.2",
"description": "Default theme for VuePress",
"main": "index.js",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vuepress.git",
"directory": "packages/@vuepress/theme-default"
},
"keywords": [
"documentation",
"vue",
"vuepress",
"generator"
],
"author": "Evan You",
"maintainers": [
{
"name": "ULIVZ",
"email": "chl814@foxmail.com"
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vuepress/issues"
},
"homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/theme-default#readme",
"dependencies": {
"@vuepress/plugin-active-header-links": "^1.0.2",
"@vuepress/plugin-nprogress": "^1.0.2",
"@vuepress/plugin-search": "^1.0.2",
"docsearch.js": "^2.5.2",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"vuepress-plugin-container": "^2.0.0"
}
}

View file

@ -0,0 +1,22 @@
@require './config'
.arrow
display inline-block
width 0
height 0
&.up
border-left 4px solid transparent
border-right 4px solid transparent
border-bottom 6px solid $arrowBgColor
&.down
border-left 4px solid transparent
border-right 4px solid transparent
border-top 6px solid $arrowBgColor
&.right
border-top 4px solid transparent
border-bottom 4px solid transparent
border-left 6px solid $arrowBgColor
&.left
border-top 4px solid transparent
border-bottom 4px solid transparent
border-right 6px solid $arrowBgColor

View file

@ -0,0 +1,140 @@
{$contentClass}
code
color lighten($textColor, 20%)
padding 0.25rem 0.5rem
margin 0
font-size 0.85em
background-color rgba(27,31,35,0.05)
border-radius 3px
.token
&.deleted
color #EC5975
&.inserted
color $accentColor
{$contentClass}
pre, pre[class*="language-"]
line-height 1.4
padding 1.25rem 1.5rem
margin 0.85rem 0
background-color $codeBgColor
border-radius 6px
overflow auto
code
color #fff
padding 0
background-color transparent
border-radius 0
div[class*="language-"]
position relative
background-color $codeBgColor
border-radius 6px
.highlight-lines
user-select none
padding-top 1.3rem
position absolute
top 0
left 0
width 100%
line-height 1.4
.highlighted
background-color rgba(0, 0, 0, 66%)
pre, pre[class*="language-"]
background transparent
position relative
z-index 1
&::before
position absolute
z-index 3
top 0.8em
right 1em
font-size 0.75rem
color rgba(255, 255, 255, 0.4)
&:not(.line-numbers-mode)
.line-numbers-wrapper
display none
&.line-numbers-mode
.highlight-lines .highlighted
position relative
&:before
content ' '
position absolute
z-index 3
left 0
top 0
display block
width $lineNumbersWrapperWidth
height 100%
background-color rgba(0, 0, 0, 66%)
pre
padding-left $lineNumbersWrapperWidth + 1 rem
vertical-align middle
.line-numbers-wrapper
position absolute
top 0
width $lineNumbersWrapperWidth
text-align center
color rgba(255, 255, 255, 0.3)
padding 1.25rem 0
line-height 1.4
br
user-select none
.line-number
position relative
z-index 4
user-select none
font-size 0.85em
&::after
content ''
position absolute
z-index 2
top 0
left 0
width $lineNumbersWrapperWidth
height 100%
border-radius 6px 0 0 6px
border-right 1px solid rgba(0, 0, 0, 66%)
background-color $codeBgColor
for lang in $codeLang
div{'[class~="language-' + lang + '"]'}
&:before
content ('' + lang)
div[class~="language-javascript"]
&:before
content "js"
div[class~="language-typescript"]
&:before
content "ts"
div[class~="language-markup"]
&:before
content "html"
div[class~="language-markdown"]
&:before
content "md"
div[class~="language-json"]:before
content "json"
div[class~="language-ruby"]:before
content "rb"
div[class~="language-python"]:before
content "py"
div[class~="language-bash"]:before
content "sh"
div[class~="language-sh"]:before
content "sh"
div[class~="language-php"]:before
content "php"
@import '~prismjs/themes/prism-tomorrow.css'

View file

@ -0,0 +1 @@
$contentClass = '.theme-default-content'

View file

@ -0,0 +1,31 @@
.custom-block
.custom-block-title
font-weight 600
margin-bottom -0.4rem
&.tip, &.warning, &.danger
padding .1rem 1.5rem
border-left-width .5rem
border-left-style solid
margin 1rem 0
&.tip
background-color #f3f5f7
border-color #42b983
&.warning
background-color rgba(255,229,100,.3)
border-color darken(#ffe564, 35%)
color darken(#ffe564, 70%)
.custom-block-title
color darken(#ffe564, 50%)
a
color $textColor
&.danger
background-color #ffe6e6
border-color darken(red, 20%)
color darken(red, 70%)
.custom-block-title
color darken(red, 40%)
a
color $textColor

View file

@ -0,0 +1,201 @@
@require './config'
@require './code'
@require './custom-blocks'
@require './arrow'
@require './wrapper'
@require './toc'
html, body
padding 0
margin 0
background-color #fff
body
font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
font-size $fontSize
color $textColor
.page
padding-left $sidebarWidth
.navbar
position fixed
z-index 20
top 0
left 0
right 0
height $navbarHeight
background-color $navbar-background
box-sizing border-box
border-bottom 1px solid $accentColor
.sidebar-mask
position fixed
z-index 9
top 0
left 0
width 100vw
height 100vh
display none
.sidebar
font-size 16px
background-color $sidebarColor
width $sidebarWidth
position fixed
z-index 10
margin 0
top $navbarHeight
left 0
bottom 0
box-sizing border-box
border-right 1px solid $borderColor
overflow-y auto
{$contentClass}:not(.custom)
@extend $wrapper
> *:first-child
margin-top $navbarHeight
a:hover
text-decoration underline
p.demo
padding 1rem 1.5rem
border 1px solid #ddd
border-radius 4px
img
max-width 100%
{$contentClass}.custom
padding 0
margin 0
img
max-width 100%
a
font-weight 500
color $accentColor
text-decoration none
p a code
font-weight 400
color $accentColor
kbd
background #eee
border solid 0.15rem #ddd
border-bottom solid 0.25rem #ddd
border-radius 0.15rem
padding 0 0.15em
blockquote
font-size 1rem
color #999;
border-left .2rem solid #dfe2e5
margin 1rem 0
padding .25rem 0 .25rem 1rem
& > p
margin 0
ul, ol
padding-left 1.2em
strong
font-weight 600
h1, h2, h3, h4, h5, h6
font-weight 600
line-height 1.25
{$contentClass}:not(.custom) > &
margin-top (0.5rem - $navbarHeight)
padding-top ($navbarHeight + 1rem)
margin-bottom 0
&:first-child
margin-top -1.5rem
margin-bottom 1rem
+ p, + pre, + .custom-block
margin-top 2rem
&:hover .header-anchor
opacity: 1
h1
font-size 2.2rem
h2
font-size 1.65rem
padding-bottom .3rem
border-bottom 1px solid $borderColor
h3
font-size 1.35rem
a.header-anchor
font-size 0.85em
float left
margin-left -0.87em
padding-right 0.23em
margin-top 0.125em
opacity 0
&:hover
text-decoration none
code, kbd, .line-number
font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace
p, ul, ol
line-height 1.7
hr
border 0
border-top 1px solid $borderColor
table
border-collapse collapse
margin 1rem 0
display: block
overflow-x: auto
tr
border-top 1px solid #dfe2e5
&:nth-child(2n)
background-color #f6f8fa
th, td
border 1px solid #dfe2e5
padding .6em 1em
.theme-container
&.sidebar-open
.sidebar-mask
display: block
&.no-navbar
{$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6
margin-top 1.5rem
padding-top 0
.sidebar
top 0
@media (min-width: ($MQMobile + 1px))
.theme-container.no-sidebar
.sidebar
display none
.page
padding-left 0
@require 'mobile.styl'

View file

@ -0,0 +1,37 @@
@require './config'
$mobileSidebarWidth = $sidebarWidth * 0.82
// narrow desktop / iPad
@media (max-width: $MQNarrow)
.sidebar
font-size 15px
width $mobileSidebarWidth
.page
padding-left $mobileSidebarWidth
// wide mobile
@media (max-width: $MQMobile)
.sidebar
top 0
padding-top $navbarHeight
transform translateX(-100%)
transition transform .2s ease
.page
padding-left 0
.theme-container
&.sidebar-open
.sidebar
transform translateX(0)
&.no-navbar
.sidebar
padding-top: 0
// narrow mobile
@media (max-width: $MQMobileNarrow)
h1
font-size 1.9rem
{$contentClass}
div[class*="language-"]
margin 0.85rem -1.5rem
border-radius 0

View file

@ -0,0 +1,32 @@
// colors
$mainColor= #6E43E8
$sidebarColor=#FAFAFC
$accentColor = #6E43E8
$textColor =rgb(51, 51, 51)
$headerColor=rgb(17, 17, 17)
$borderColor = #e8e8fb
$codeBgColor = #282c34
$arrowBgColor = #ccc
$navbar-background = #6E43E8;
$navbar-text-color = #ffffff;
$section-color=#f7f7f7;
$fontSize = 16px
// layout
$navbarHeight = 5.2rem
$navbar-vertical-padding = 1.4rem;
$navbar-horizontal-padding = 1.4rem;
$sidebarWidth = 16rem
$contentWidth = 960px
// // responsive breakpoints
$MQNarrow = 959px
$MQMobile = 719px
$MQMobileNarrow = 419px
.icon.outbound
display none !important

View file

@ -0,0 +1,3 @@
.table-of-contents
.badge
vertical-align middle

View file

@ -0,0 +1,9 @@
$wrapper
max-width $contentWidth
margin 0 auto
padding 2rem 2.5rem
@media (max-width: $MQNarrow)
padding 2rem
@media (max-width: $MQMobileNarrow)
padding 1.5rem

View file

@ -0,0 +1,247 @@
export const hashRE = /#.*$/;
export const extRE = /\.(md|html)$/;
export const endingSlashRE = /\/$/;
export const outboundRE = /^(https?:|mailto:|tel:|[a-zA-Z]{4,}:)/;
export function normalize(path) {
return decodeURI(path)
.replace(hashRE, "")
.replace(extRE, "");
}
export function getHash(path) {
const match = path.match(hashRE);
if (match) {
return match[0];
}
}
export function isExternal(path) {
return outboundRE.test(path);
}
export function isMailto(path) {
return /^mailto:/.test(path);
}
export function isTel(path) {
return /^tel:/.test(path);
}
export function ensureExt(path) {
if (isExternal(path)) {
return path;
}
const hashMatch = path.match(hashRE);
const hash = hashMatch ? hashMatch[0] : "";
const normalized = normalize(path);
if (endingSlashRE.test(normalized)) {
return path;
}
return normalized + ".html" + hash;
}
export function isActive(route, path) {
const routeHash = route.hash;
const linkHash = getHash(path);
if (linkHash && routeHash !== linkHash) {
return false;
}
const routePath = normalize(route.path);
const pagePath = normalize(path);
return routePath === pagePath;
}
export function resolvePage(pages, rawPath, base) {
if (isExternal(rawPath)) {
return {
type: "external",
path: rawPath
};
}
if (base) {
rawPath = resolvePath(rawPath, base);
}
const path = normalize(rawPath);
for (let i = 0; i < pages.length; i++) {
if (normalize(pages[i].regularPath) === path) {
return Object.assign({}, pages[i], {
type: "page",
path: ensureExt(pages[i].path)
});
}
}
console.error(
`[vuepress] No matching page found for sidebar item "${rawPath}"`
);
return {};
}
function resolvePath(relative, base, append) {
const firstChar = relative.charAt(0);
if (firstChar === "/") {
return relative;
}
if (firstChar === "?" || firstChar === "#") {
return base + relative;
}
const stack = base.split("/");
// remove trailing segment if:
// - not appending
// - appending to trailing slash (last segment is empty)
if (!append || !stack[stack.length - 1]) {
stack.pop();
}
// resolve relative path
const segments = relative.replace(/^\//, "").split("/");
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (segment === "..") {
stack.pop();
} else if (segment !== ".") {
stack.push(segment);
}
}
// ensure leading slash
if (stack[0] !== "") {
stack.unshift("");
}
return stack.join("/");
}
/**
* @param { Page } page
* @param { string } regularPath
* @param { SiteData } site
* @param { string } localePath
* @returns { SidebarGroup }
*/
export function resolveSidebarItems(page, regularPath, site, localePath) {
const { pages, themeConfig } = site;
const localeConfig =
localePath && themeConfig.locales
? themeConfig.locales[localePath] || themeConfig
: themeConfig;
const pageSidebarConfig =
page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar;
if (pageSidebarConfig === "auto") {
return resolveHeaders(page);
}
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar;
if (!sidebarConfig) {
return [];
} else {
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig);
return config ? config.map(item => resolveItem(item, pages, base)) : [];
}
}
/**
* @param { Page } page
* @returns { SidebarGroup }
*/
function resolveHeaders(page) {
const headers = groupHeaders(page.headers || []);
return [
{
type: "group",
collapsable: false,
title: page.title,
path: null,
children: headers.map(h => ({
type: "auto",
title: h.title,
basePath: page.path,
path: page.path + "#" + h.slug,
children: h.children || []
}))
}
];
}
export function groupHeaders(headers) {
// group h3s under h2
headers = headers.map(h => Object.assign({}, h));
let lastH2;
headers.forEach(h => {
if (h.level === 2) {
lastH2 = h;
} else if (lastH2) {
(lastH2.children || (lastH2.children = [])).push(h);
}
});
return headers.filter(h => h.level === 2);
}
export function resolveNavLinkItem(linkItem) {
return Object.assign(linkItem, {
type: linkItem.items && linkItem.items.length ? "links" : "link"
});
}
/**
* @param { Route } route
* @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
* @returns { base: string, config: SidebarConfig }
*/
export function resolveMatchingConfig(regularPath, config) {
if (Array.isArray(config)) {
return {
base: "/",
config: config
};
}
for (const base in config) {
if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
return {
base,
config: config[base]
};
}
}
return {};
}
function ensureEndingSlash(path) {
return /(\.html|\/)$/.test(path) ? path : path + "/";
}
function resolveItem(item, pages, base, groupDepth = 1) {
if (typeof item === "string") {
return resolvePage(pages, item, base);
} else if (Array.isArray(item)) {
return Object.assign(resolvePage(pages, item[0], base), {
title: item[1]
});
} else {
if (groupDepth > 3) {
console.error("[vuepress] detected a too deep nested sidebar group.");
}
const children = item.children || [];
if (children.length === 0 && item.path) {
return Object.assign(resolvePage(pages, item.path, base), {
title: item.title
});
}
return {
type: "group",
path: item.path,
title: item.title,
sidebarDepth: item.sidebarDepth,
children: children.map(child =>
resolveItem(child, pages, base, groupDepth + 1)
),
collapsable: item.collapsable !== false
};
}
}

View file

@ -1,3 +1,7 @@
---
title: Code of Conduct
---
# Contributor Covenant Code of Conduct
## Our Pledge

View file

@ -0,0 +1,43 @@
---
title: Contributing
description: >-
This document describes how you can find issues to work on, setup Pomerium
locally for development, and get help when you are stuck.
---
# Contributing
You can have a direct impact on the project by helping with its code or documentation. To contribute to Pomerium, open a [pull request](https://github.com/pomerium/pomerium/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.**
## Code
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions--even if it seems small or insignificant. Please don't take it personally. :wink: If your change is on the right track, we can guide you to make it mergable.
Here are some of the expectations we have of contributors:
- If your change is more than just a minor alteration, **open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that changes are in-line with the project's goals and the best interests of its users. If there's already an issue about it, comment on the existing issue to claim it.
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we DON'T do.](https://twitter.com/iamdevloper/status/397664295875805184)
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
- **Own your contributions.** Pomerium is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
- **Recommended reading**
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
## Documentation
Pomerium's documentation is available at <https://www.pomerium.io/docs>. If you would like to make a fix to the docs, please submit an issue here describing the change to make.

View file

@ -0,0 +1,145 @@
# Development Guide
The following guide assumes you do _not_ want to expose your development server to the public internet and instead want to do everything, with the exception of identity provider callbacks, locally.
If you are comfortable with a public development configuration, see the Synology quick-start which covers how to set up your network, domain, and retrieve wild-card certificates from LetsEncrypt, the only difference being you would route traffic to your local development machine instead of the docker image.
## Build From Source
The following quick-start guide covers how to retrieve and build Pomerium from its source-code as well as how to run Pomerium using a minimal but complete configuration. One of the benefits of compiling from source is that Go supports building static binaries for a [wide array of architectures and operating systems](https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63) -- some of which may not yet be supported by Pomerium's official images or binaries.
### Prerequisites
- [git](https://git-scm.com/)
- [go](https://golang.org/doc/install) programming language
- A configured [identity provider]
- A [wild-card TLS certificate]
### Download
Retrieve the latest copy of pomerium's source code by cloning the repository.
```bash
git clone https://github.com/pomerium/pomerium.git $HOME/pomerium
```
### Make
Build pomerium from source in a single step using make.
```bash
cd $HOME/pomerium
make
```
[Make] will run all the tests, some code linters, then build the binary. If all is good, you should now have a freshly built pomerium binary for your architecture and operating system in the `pomerium/bin` directory.
### Configure
Pomerium supports setting [configuration variables] using both environmental variables and using a configuration file.
### Configuration file
Create a config file (`config.yaml`). This file will be use to determine Pomerium's configuration settings, routes, and access-policies. Consider the following example:
<<< @/docs/docs/reference/examples/config/config.minimal.yaml
### Environmental Variables
As mentioned above, Pomerium supports mixing and matching where configuration details are set. For example, we can specify our secret values and domains certificates as [environmental configuration variables].
<<< @/docs/docs/reference/examples/config/config.minimal.env
### Run
Finally, source the the configuration `env` file and run pomerium specifying the configuration file `config.yaml`.
```bash
source ./env
./bin/pomerium -config config.yaml
```
### Navigate
Browse to `external-httpbin.your.domain.example`. Connections between you and [httpbin] will now be proxied and managed by Pomerium.
## Domains
Publicly resolvable domains are central in how pomerium works. For local development, we'll have to do some additional configuration to mock that public workflow on our local machine.
### Pick an identity provider friendly domain name
Though typically you would want to use one of the TLDs specified by [RFC-2606](http://tools.ietf.org/html/rfc2606) for testing, unfortunately, google explicitly does not support oauth calls to test domains. As such, it's recommended to use a domain you control using a wildcard-subdomain that you know will not be used.
If you do not control a domain, you can use `*.localhost.pomerium.io` which I've established for this use Plus, if you _do_ have internet access, this domain already has a [public A record](https://en.wikipedia.org/wiki/List_of_DNS_record_types) pointing to localhost.
### Wildcard domain resolution with `dnsmasq`
If you are on a plane (for example), you may not be able to access public DNS. Unfortunately, `/etc/hosts` does not support wildcard domains and would require you specifying a new entry for each pomerium managed route. The workaround is to use [dnsmasq](https://en.wikipedia.org/wiki/Dnsmasq) locally which _does_ support local resolution of wildcard domains.
### OSX
1. Install `brew update && brew install dnsmasq`
2. Edit `/usr/local/etc/dnsmasq.conf` to tell dnsmasq to resolve your test domains.
```bash
echo 'address=/.localhost.pomerium.io/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
```
```bash
sudo mkdir -pv /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/localhost.pomerium.io'
```
1. Restart `dnsmasq`
```bash
sudo brew services restart dnsmasq
```
1. Tell OSX to use `127.0.0.1` as a the primary DNS resolver (followed by whatever public DNS you are using). ![osx dns resolution](./img/local-development-osx-dns.png)
### Locally trusted wildcard certificates
In production, we'd use a public certificate authority such as LetsEncrypt. For local development, enter [mkcert](https://mkcert.dev/) which is a "simple zero-config tool to make locally trusted development certificates with any names you'd like."
1. Install `mkcert`.
```bash
go get -u github.com/FiloSottile/mkcert
```
1. Bootstrap `mkcert`'s root certificate into your operating system's trust store.
```bash
mkcert -install
```
1. Create your wildcard domain.
```bash
mkcert "*.localhost.pomerium.io"
```
1. Viola! Now you can use locally trusted certificates with pomerium!
| Setting | Certificate file location |
| ---------------------------- | ------------------------------------------- |
| `certificate_file` | `./_wildcard.localhost.pomerium.io-key.pem` |
| `certificate_key_file` | `./_wildcard.localhost.pomerium.io.pem` |
| `certificate_authority_file` | `$(mkcert -CAROOT)/rootCA.pem` |
See also:
- [Set up a local test domain with dnsmasq](https://github.com/aviddiviner/til/blob/master/devops/set-up-a-local-test-domain-with-dnsmasq.md)
- [USE DNSMASQ INSTEAD OF /ETC/HOSTS](https://www.stevenrombauts.be/2018/01/use-dnsmasq-instead-of-etc-hosts/)
- [How to setup wildcard dev domains with dnsmasq on a mac](https://hedichaibi.com/how-to-setup-wildcard-dev-domains-with-dnsmasq-on-a-mac/)
- [mkcert](https://github.com/FiloSottile/mkcert) is a simple tool for making locally-trusted development certificates
[configuration variables]: ../reference/readme.md
[download]: https://github.com/pomerium/pomerium/releases
[environmental configuration variables]: https://12factor.net/config
[httpbin]: https://httpbin.org/
[identity provider]: ../identity-providers/readme.md
[make]: https://en.wikipedia.org/wiki/Make_(software)
[wild-card tls certificate]: ../reference/certificates.md

View file

@ -44,13 +44,13 @@ Pomerium supports setting [configuration variables] using both environmental var
Create a config file (`config.yaml`). This file will be use to determine Pomerium's configuration settings, routes, and access-policies. Consider the following example:
<<< @/docs/docs/examples/config/config.minimal.yaml
<<< @/docs/docs/reference/examples/config/config.minimal.yaml
### Environmental Variables
As mentioned above, Pomerium supports mixing and matching where configuration details are set. For example, we can specify our secret values and domains certificates as [environmental configuration variables].
<<< @/docs/docs/examples/config/config.minimal.env
<<< @/docs/docs/reference/examples/config/config.minimal.env
## Run
@ -65,10 +65,10 @@ source ./env
Browse to `external-httpbin.your.domain.example`. Connections between you and [httpbin] will now be proxied and managed by Pomerium.
[configuration variables]: ../reference/readme.md
[configuration variables]: ../reference/reference.md
[download]: https://github.com/pomerium/pomerium/releases
[environmental configuration variables]: https://12factor.net/config
[httpbin]: https://httpbin.org/
[identity provider]: ../docs/identity-providers.md
[identity provider]: ../docs/identity-providers/
[make]: https://en.wikipedia.org/wiki/Make_(software)
[wild-card tls certificate]: ../docs/certificates.md
[wild-card tls certificate]: ../reference/certificates.md

View file

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

24
docs/community/readme.md Normal file
View file

@ -0,0 +1,24 @@
---
title: Overview
description: >-
This document describes how you can find issues to work on, setup Pomerium
locally for development, and get help when you are stuck.
---
# Get help
If you have a question about using Pomerium, [join our slack channel](http://slack.pomerium.io/)! There will be more people there who can help you than just the Pomerium developers who follow our issue tracker. Issues are not the place for usage questions.
# Report bugs
Like every software, Pomerium has its flaws. If you find one, [search the issues](https://github.com/pomerium/pomerium/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/pomerium/pomerium/issues/new) and describe the bug, and somebody will look into it!
Please follow the issue template so we have all the needed information. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. The more detailed and specific you are, the faster we will be able to help you!
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
# Suggest features
First, [search to see if your feature has already been requested](https://github.com/pomerium/pomerium/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. You don't have to follow the bug template for feature requests. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and without clarification will have to be closed.
While we really do value your requests and implement many of them, not all features are a good fit for Pomerium. But if a feature is not in the best interest of the Pomerium project or its users in general, we may politely decline to implement it into Pomerium core.

View file

@ -0,0 +1,61 @@
# Security
We deeply appreciate any effort to discover and disclose security vulnerabilities responsibly.
If you would like to report a vulnerability, or have any security concerns, please e-mail info@pomerium.io or reach out on [keybase](https://keybase.io/bdesimone).
Though we accept PGP-encrypted email, please only use it for critical security reports.
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org
xsFNBFuDBCsBEADmvRj1ooWDgyisMiyUvOIFq2l52r2gD2bo6I9RyZUFCm5CO0Ye
rk4POVtG/NPwbvd4dSmA7ePQLWNoMx4bN42B4EUJgqh+U82NKu0qU4eVeew4x+w1
bNmsqa0ZdoSMqONofFoD/ImepOVkZx56LPIJ7hb4/JlYnpPFlphfj06bf8JEqcGI
WgvJcZdXhSS2RDkSfC34EXps6w9aWmgDZKWz56YRcTVPzGJuGw1mfJLL1F9NQq/g
nzW82j+Z9bjVdeVLuEH3QBuKoviyVoIjIJvSCtb92151PMsvRTFpeTbp45Lep+xc
RVGEKhXPW7AA9n3Q57Y0cxWKgSE0agnsjpzUOTMbwl3VyxuwWyxuP2JpGGXXiX9y
4uE27FOb2u8N8WbTVueTKNs2QgqukKcg0XX7b2UpWX4OkhD/U5Nbh3jAvZ9COoK5
TIb/NgJqnMo/ReKFRA8IgXIoKeGn/WJJCe6nPAo+6c+glam9xekHbdH/9PQ5eSOf
lMfzgNXd2OOLYK98KQpRqWIdMqWlt3Ufik+cbsfCnaK9rK4ktiYZdiDHK+Lp7V71
Ng45o/sHnnSjvYKlhBn5EcdpVXw6IKrUW9OUD7l/sga+xa0MMmUF4C2VxYJ+n6Qg
bRZaREvKLbhsqycmq4p+oBpSjyWgP4CRPHkG03PYNFA1/cg7sFUUekmehQARAQAB
zShCb2JieSBEZVNpbW9uZSA8Ym9iYnlkZXNpbW9uZUBnbWFpbC5jb20+wsF4BBMB
CAAsBQJbgwQrCRCu5M8S/obQfgIbAwUJHhM4AAIZAQQLBwkDBRUICgIDBBYAAQIA
AHd0EABezUgLsjeCLDK3JG4VkJkvDAZNKLtzEjZ2pdexWjzREgYvu42d3QNM3fKI
kW6TTb7C08BsiijGaUqtZUCyqH/dN24jw5a4nKKbnqylDUr2XCpWwKVbsF4t+BXR
jADJeRLP+cMbHhLb8CindOo2ZRrzMp912454sCGKw3c27P5NTKJcO9WGArQ39MEl
C2MqIQREdBrkfQsXK7rz26SSqlyrNl7NQDmKRMZLciaibgEP4rfycqierqcDZiTP
2xxTckB4tV3K5ki3s5NV+cYnq38efmUxygnU8wlzbcv9MukvAOLLLKEiSxBzgpZb
ddr8QC/ljmvzGm2qKQFCjBaV4wtk1n6xZ8AjjpP8irxFQwwCxwNEIwx6vt3NQNxm
qL8KXVn617mOc6iS9BvZVzcBzXUh8geIDt7Chqil8kUuPiCpVpY63z+phLHcAen/
NHFJ3OE/CbUcBsw0xDfKF+NWp7hQjbk5lV1ueXV2FTJ/SISEvuJ64CELzCPzGwwE
7Gb0zwOeIMBAJMrPEt+YByu0dxa9vjcgOLeaRzuADtRvJCl3UjoXDC8Vdii1ywBM
wkZcvfW51MOiiKFadZsYjzgBFIJ9rybXyxx8kfzTMpcmGLa7v2zp1+ANZm4Wwb8Z
zJgU+MLlbjJcXIqbdhjC7cgL/1YitXWw1ELDP4F8taV4aWK62M7BTQRbgwQrARAA
pza3CTXb5GUKeBM8YB1Wv5MIauL/bfpCZo3ujhJaN87XtRBQXMfDyznCThz5vraZ
HWpvLQcsaJoMPbC7UbUl2l9yiCCd0y4/b2czzpA1P4rTa6FrSWl4xFi+WLlPiCls
m7xEizBU0PcqsDEGX61o+S2Iiay2jjpOGlDNs3z6gyyGNvjjRd2aRjAACGqqOH75
J+6a4dwISUQ9zP+JkWsmgSZw10PhS4LemXUN2XyIMbJdWKbej8vPjyFXgwjKkBT5
/RCgNGeE+hji/p22DhTIsCOMzVW6nch9B6uXMtpbqtily+hqYkhT9Ke6fInniafN
N8DuFH7YIixbWx9+kg8kRKAknMuqWS/u2d6QZD8lI6uUDO4/EuCaek/oCmJ8aQ+x
kQNMYRbnVlDQ+/WYepnF6nsQgsDELcAJAkNMXm0jnfcfCtZNuh79H6b1yvrPTkB0
2uawLA0NvdVKpv9ZPZy7RLoytVspYUA+T0khcSozzBcjyE9jvd7bic+biIeXyYe2
Zu3KevuvsiLEvifhjAg0FbML/GOYZbayxpe1IWiqzRsq/UX+2E8PJV2NuqbFOj8U
93Jgol+Ag8JAsmnFrJCtKs5diDOS/wd+hljZyuWcWQCaahsFoKMV1ayoVbOJ1XWU
3PAh30enHcGeIg6sV32xhYBO7mTnX51VybRRMAtd4hMAEQEAAcLBdQQYAQgAKQUC
W4MEKwkQruTPEv6G0H4CGwwFCR4TOAAECwcJAwUVCAoCAwQWAAECAACNdxAA3s5s
mvlKZrm5dfBqzCNDQtJtqqFkcOBCNhMKsJKn81YKsvT0yHsj6rfO5hL2uu6NKjkR
K1Dn9IAR2wBt0pJy2bJo9HGfqAxb0JaC6Rgu/MoEYTcRbGUl3N6ywBAUFJ31Ou5F
chzDrJJ37kLjTTHxkW8UXlVZWRs+jVwTTjWL96UXVxYdndeAAxLgceRy0h2h00xF
PoVsjEpoek+yaHhmLWC3wSZ0jveGcB0pT9BI7D/9FZVHQ0DPzlYaXT6eZSLv+5BE
dr+Gv4iwJ0DLF6tHl7bEm1O2iS3PyU59Fu5GOV2R6b/NRW+pYUwZhFz3zQ7GkUJE
V+XBOMUFq5VduuzXZKSmlqr4SSx9SvcDiH7eRjNTX4Hzb+VcWKS/bvSS0efwz5AW
Q9zObT1B/c889rPoiTIDXI4qOhzPmeva89QceRo04QXzi8fujRJoAmqdzW8uiiKO
Edk1J5rzMkfEHMVf1l8z390qNy3VAk++mqQe8ZS2W7/ulNzNt3Gwx54rdOEe5pIl
2QSGEwZgg6zX7C94xlqnxp84axNQghWJfBolMcp0q/yDFjbnRzd2vLUhtzEAosd4
VDw98WyFTbRTTN8ElRptLUsa73raYpKRXN17vB517spEghyT1oyCdHYgaqvRkU7b
ZDRB+exOyJJypi2cSaarxiI2gaMT2wp+dChnQ4k=
=LGUI
-----END PGP PUBLIC KEY BLOCK-----
```

View file

@ -1,4 +1,4 @@
# Pomerium Changelog
# Changelog
## v0.2.0
@ -25,6 +25,10 @@
- Improved HTTP metrics implementation internals
- The HTTP method label is now `http_method`, and HTTP status label is now `http_status`
### FIXED
- Fixed potential race condition when signing requests. [GH-240]
### Changed
- GRPC version upgraded to v1.22 [GH-219]
@ -116,8 +120,8 @@
### DOCUMENTATION
- Added [synology tutorial](https://www.pomerium.io/guide/synology.html). [GH-96]
- Added [certificates documentation](https://www.pomerium.io/docs/certificates.html). [GH-79]
- Added [synology tutorial]. [GH-96]
- Added [certificates documentation]. [GH-79]
## v0.0.3
@ -154,3 +158,6 @@
### FIXED
- `http.Server` and `httputil.NewSingleHostReverseProxy` now uses pomerium's logging package instead of the standard library's built in one. [GH-58]
[synology tutorial]: ./quick-start/synology.md
[certificates documentation]: ../reference/certificates.md

86
docs/docs/background.md Normal file
View file

@ -0,0 +1,86 @@
---
title: Background
lang: en-US
meta:
- name: keywords
content: pomerium identity-access-proxy beyondcorp zero-trust reverse-proxy ztn
---
# Background
## History
For years, security was synonymous with network security. Firewalls, network segmentation, and VPNs reigned the day. Broadly speaking, that network focused security posture is what people mean today when they talk about the perimeter security model. So-called "impenetrable fortress" security worked well for a period of time when you could reasonably expect your network perimeter to correspond to an actual physical perimeters, users, devices, and servers. But as teams, applications, workloads, and users became more ephemeral and distributed, the shortcomings of perimeter based security have become more apparent in terms of both operational costs and security breaches.
> Most networks [have] big castle walls, hard crunchy outer shell, and soft gooey centers...
>
> [Rob Joyce](https://en.wikipedia.org/wiki/Rob_Joyce) [Chief of Tailored Access Operations](https://en.wikipedia.org/wiki/Tailored_Access_Operations), [National Security Agency @ ENIGMA 2016](https://www.youtube.com/watch?v=bDJb8WOJYdA&feature=youtu.be&t=1627)
There's no such thing as perfect security. Many recent high-profile breaches have demonstrated just how difficult it is for even large companies with sophisticated security organizations to avoid a breach. To pick just two of many possible breaches were perimeter security played a role, consider the Target and Google hacks. In Target's case, hackers circumvented both the physical and network perimeter by [hacking the HVAC system](https://krebsonsecurity.com/2014/02/target-hackers-broke-in-via-hvac-company/) which was connected to the internal corporate network and then moved laterally to exfiltrate customer credit card data. In Google's case, they experienced a devastating attack at the hands of the Chinese military. Google did a bottom up review of their security posture following [Operation Aurora](https://en.wikipedia.org/wiki/Operation_Aurora). The resulting actions from that review would be released as a [series of white papers](https://ai.google/research/pubs/pub43231) called "BeyondCorp" which have since become foundational documents in articulating how and why an organization could move beyond corporate perimeter (BeyondCorp...get it?) based security.
> In reality, there's never one front door; there are many front doors...[and] ... we're not securing a single castle. We're starting to think about securing many different interconnected castles.
>
> [Armon Dadgar, Cofounder of HashiCorp @ PagerDuty Nov 2018](https://www.hashicorp.com/resources/how-zero-trust-networking)
The other side of the security trade-off is operational agility. Perimeter based approaches tend to focus on network segmentation which entails creating virtual or physical boundaries around services that need to communicate. Making those boundaries is increasingly difficult to manage in a world of microservices, and cloud computing where service communication requirements are constantly in flux. In theory, an organization could "micro/nano/pico-segment" each and every layer of an application stack to ensure the appropriate audience, however, in practice, operators usually choose between a very precise boundary that is high-touch, time-consuming to mange, and error prone, and that of a more lax boundary that may entail more risk but is less time consuming to update and manage and less prone to break.
### Gaps in the perimeter
Perimeter based security suffers from the following shortcomings:
- Perimeter security largely ignores the insider threat. Given that thirty percent of all breaches are from internal actors, this is a pretty big omission.
- If the last few years have proved anything it is that the impenetrable fortress theory fails in practice even for the most sophisticated of security organizations.
- Network segmentation is a time-consuming, and difficult to get exactly right mechanism for ensuring secure communication.
- Even just defining what the network perimeter is is an increasingly difficult proposition in a remote-work, BYOD, multi-cloud world. Most organizations are a heterogeneous mix of clouds, servers, devices, and organizational units.
- VPNs are often misused and exacerbate the issue it by opening yet another door into your network organization.
### Zero-trust, behind the gates
[Zero-trust](https://ldapwiki.com/wiki/Zero%20Trust) instead attempts to mitigate these shortcomings by adopting the following principles:
- Trust flows from identity, device-state, and context; not network location.
- Treat both internal and external networks as completely untrusted. Mutually authenticated encryption is used instead of network segmentation.
- Act like you are already breached, because you probably are, and an attacker could be anyone, and anywhere on your network.
- Every device, user, and application's communication should be authenticated, authorized, and encrypted. Access policy should be dynamic, and built from multiple sources.
To be clear, perimeter security is not defunct, nor is zero-trust security a panacea or a single product. Many of the ideas and principles of perimeter security are still relevant and are part of a holistic, and wide-ranging security policy. After all, we still want our castles to have high walls.
### Where Pomerium Fits
So to put all this back in context, before zero-trust tools like Pomerium existed, access to internal applications were gated by whether a user was on the corporate network or not. Trust flowed and was anchored to the security of the perimeter. For all the reasons discussed above, this has turned to be a lacking security model. In contrast, Pomerium adopts the zero-trust stance and uses identity, device-state, and context compared against a single-source of rich authorization policy as the basis for delegating access to an internal resource. All Pomerium communication is mutually authenticated and encrypted, there is no trust belied to internal vs external network.
## Further reading
Pomerium was inspired by the security model originally articulated by [John Kindervag](http://www.virtualstarmedia.com/downloads/Forrester_zero_trust_DNA.pdf) in 2010, and by Google in 2011 as a result of the [Operation Aurora](https://en.wikipedia.org/wiki/Operation_Aurora) breach. What follows is a curated list of books, papers, posts, and videos that covers the topic in more depth.
### Books
- ⭐[Zero Trust Networks](http://shop.oreilly.com/product/0636920052265.do) by Gilman and Barth
- [Site Reliability Engineering: How Google Runs Production Systems](https://www.amazon.com/Site-Reliability-Engineering-Production-Systems/dp/149192912X)
### Papers
- Forrester [Build Security Into Your Network's DNA: The Zero Trust Network Architecture](http://www.virtualstarmedia.com/downloads/Forrester_zero_trust_DNA.pdf)
- ⭐Google BeyondCorp 1 [An overview: "A New Approach to Enterprise Security"](https://research.google.com/pubs/pub43231.html)
- Google BeyondCorp 2 [How Google did it: "Design to Deployment at Google"](https://research.google.com/pubs/pub44860.html)
- ⭐Google BeyondCorp 3 [Google's front-end infrastructure: "The Access Proxy"](https://research.google.com/pubs/pub45728.html)
- Google BeyondCorp 4 [Migrating to BeyondCorp: Maintaining Productivity While Improving Security](https://research.google.com/pubs/pub46134.html)
- Google BeyondCorp 5 [The human element: "The User Experience"](https://research.google.com/pubs/pub46366.html)
- Google BeyondCorp 6 [Secure your endpoints: "Building a Healthy Fleet"](https://ai.google/research/pubs/pub47356)
### Posts
- Google [Securing your business and securing your fleet the BeyondCorp way](https://cloud.google.com/blog/products/identity-security/securing-your-business-and-securing-your-fleet-the-beyondcorp-way)
- Google [Preparing for a BeyondCorp world: Understanding your device inventory](https://cloud.google.com/blog/products/identity-security/preparing-beyondcorp-world-understanding-your-device-inventory)
- Google [How BeyondCorp can help businesses be more productive](https://www.blog.google/products/google-cloud/how-beyondcorp-can-help-businesses-be-more-productive/)
- Google [How to use BeyondCorp to ditch your VPN, improve security and go to the cloud](https://www.blog.google/products/google-cloud/how-use-beyondcorp-ditch-your-vpn-improve-security-and-go-cloud/)
- Wall Street Journal [Google Moves Its Corporate Applications to the Internet](https://blogs.wsj.com/cio/2015/05/11/google-moves-its-corporate-applications-to-the-internet/)
### Videos
- [USENIX Enigma 2016 - NSA TAO Chief on Disrupting Nation State Hackers](https://youtu.be/bDJb8WOJYdA?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf)
- [What, Why, and How of Zero Trust Networking](https://youtu.be/eDVHIfVSdIo?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf) by Armon Dadgar, Hashicorp
- [O'Reilly Security 2017 NYC Beyondcorp: Beyond Fortress Security](https://youtu.be/oAvDASLehpY?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf) by Neal Muller, Google
- [Be Ready for BeyondCorp: enterprise identity, perimeters and your application](https://youtu.be/5UiWAlwok1s?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf) by Jason Kent
- ⭐️ [OAuth 2.0 and OpenID Connect (in plain English)
](https://www.youtube.com/watch?v=996OiexHze0) by Nate Barbettini

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

View file

@ -1,152 +0,0 @@
---
title: Contributing
description: >-
This document describes how you can find issues to work on, setup Pomerium
locally for development, and get help when you are stuck.
---
# Contributing to Pomerium
Thanks for your interest in contributing to Pomerium! We welcome all contributions, from new features to documentation updates. This document describes how you can find issues to work on, setup Pomerium locally for development, and get help when you are stuck.
## Contributing code
You can have a direct impact on the project by helping with its code. To contribute code to Pomerium, open a [pull request](https://github.com/pomerium/pomerium/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.**
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions--even if it seems small or insignificant. Please don't take it personally. :wink: If your change is on the right track, we can guide you to make it mergable.
Here are some of the expectations we have of contributors:
- If your change is more than just a minor alteration, **open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that changes are in-line with the project's goals and the best interests of its users. If there's already an issue about it, comment on the existing issue to claim it.
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we DON'T do.](https://twitter.com/iamdevloper/status/397664295875805184)
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
- **Own your contributions.** Pomerium is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
- **Recommended reading**
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
## Getting help using Pomerium
If you have a question about using Pomerium, [join our slack channel](http://slack.pomerium.io/)! There will be more people there who can help you than just the Pomerium developers who follow our issue tracker. Issues are not the place for usage questions.
## Reporting bugs
Like every software, Pomerium has its flaws. If you find one, [search the issues](https://github.com/pomerium/pomerium/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/pomerium/pomerium/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Pomerium, not plugins.)
**You can help stop bugs in their tracks!** Speed up the patch by identifying the bug in the code. This can sometimes be done by adding `fmt.Println()` statements (or similar) in relevant code paths to narrow down where the problem may be. It's a good way to [introduce yourself to the Go language](https://tour.golang.org), too.
Please follow the issue template so we have all the needed information. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. The burden is on you to convince us that it is actually a bug in Pomerium. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you!
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
Please be kind. :smile: Remember that Pomerium comes at no cost to you, and you're getting free support when we fix your issues. If we helped you, please consider helping someone else!
## Suggesting features
First, [search to see if your feature has already been requested](https://github.com/pomerium/pomerium/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. You don't have to follow the bug template for feature requests. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and without clarification will have to be closed.
While we really do value your requests and implement many of them, not all features are a good fit for Pomerium. But if a feature is not in the best interest of the Pomerium project or its users in general, we may politely decline to implement it into Pomerium core.
## Improving documentation
Pomeriums's documentation is available at <https://www.pomerium.io>. If you would like to make a fix to the docs, please submit an issue here describing the change to make.
## Responsible Disclosure
We deeply appreciate any effort to discover and disclose security vulnerabilities responsibly.
If you would like to report a vulnerability, or have any security concerns, please e-mail info@pomerium.io or reach out to me on [keybase](https://keybase.io/bdesimone) .
## Developers Guide
The following guide assumes you do _not_ want to expose your development server to the public internet and instead want to do everything, with the exception of identity provider callbacks, locally.
If you are comfortable with a public development configuration, see the Synology quick-start which covers how to set up your network, domain, and retrieve wild-card certificates from LetsEncrypt, the only difference being you would route traffic to your local development machine instead of the docker image.
### Domains
Publicly resolvable domains are central in how pomerium works. For local development, we'll have to do some additional configuration to mock that public workflow on our local machine.
### Pick an identity provider friendly domain name
Though typically you would want to use one of the TLDs specified by [RFC-2606](http://tools.ietf.org/html/rfc2606) for testing, unfortunately, google explicitly does not support oauth calls to test domains. As such, it's recommended to use a domain you control using a wildcard-subdomain that you know will not be used.
If you do not control a domain, you can use `*.localhost.pomerium.io` which I've established for this use Plus, if you _do_ have internet access, this domain already has a [public A record](https://en.wikipedia.org/wiki/List_of_DNS_record_types) pointing to localhost.
### Wildcard domain resolution with `dnsmasq`
If you are on a plane (for example), you may not be able to access public DNS. Unfortunately, `/etc/hosts` does not support wildcard domains and would require you specifying a new entry for each pomerium managed route. The workaround is to use [dnsmasq](https://en.wikipedia.org/wiki/Dnsmasq) locally which _does_ support local resolution of wildcard domains.
#### OSX
1. Install `brew update && brew install dnsmasq`
2. Edit `/usr/local/etc/dnsmasq.conf` to tell dnsmasq to resolve your test domains.
```bash
echo 'address=/.localhost.pomerium.io/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
```
```bash
sudo mkdir -pv /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/localhost.pomerium.io'
```
3. Restart `dnsmasq`
```bash
sudo brew services restart dnsmasq
```
4. Tell OSX to use `127.0.0.1` as a the primary DNS resolver (followed by whatever public DNS you are using). ![osx dns resolution](./local-development/local-development-osx-dns.png)
### Locally trusted wildcard certificates
In production, we'd use a public certificate authority such as LetsEncrypt. For local development, enter [mkcert](https://mkcert.dev/) which is a "simple zero-config tool to make locally trusted development certificates with any names you'd like."
1. Install `mkcert`.
```bash
go get -u github.com/FiloSottile/mkcert
```
2. Bootstrap `mkcert`'s root certificate into your operating system's trust store.
```bash
mkcert -install
```
3. Create your wildcard domain.
```bash
mkcert "*.localhost.pomerium.io"
```
4. Viola! Now you can use locally trusted certificates with pomerium!
Setting | Certificate file location
---------------------------- | -------------------------------------------
`certificate_file` | `./_wildcard.localhost.pomerium.io-key.pem` |
`certificate_key_file` | `./_wildcard.localhost.pomerium.io.pem` |
`certificate_authority_file` | `$(mkcert -CAROOT)/rootCA.pem` |
See also:
- [Set up a local test domain with dnsmasq](https://github.com/aviddiviner/til/blob/master/devops/set-up-a-local-test-domain-with-dnsmasq.md)
- [USE DNSMASQ INSTEAD OF /ETC/HOSTS](https://www.stevenrombauts.be/2018/01/use-dnsmasq-instead-of-etc-hosts/)
- [How to setup wildcard dev domains with dnsmasq on a mac](https://hedichaibi.com/how-to-setup-wildcard-dev-domains-with-dnsmasq-on-a-mac/)
- [mkcert](https://github.com/FiloSottile/mkcert) is a simple tool for making locally-trusted development certificates

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

View file

@ -1,364 +0,0 @@
---
title: Identity Providers
description: >-
This article describes how to connect Pomerium to third-party identity
providers / single-sign-on services. You will need to generate keys, copy
these into your Pomerium settings, and enable the connection.
---
# Identity Provider Configuration
This article describes how to configure Pomerium to use a third-party identity service for single-sign-on.
There are a few configuration steps required for identity provider integration. Most providers support [OpenID Connect] which provides a standardized identity and authentication interface.
In this guide we'll cover how to do the following for each identity provider:
1. Set a **Redirect URL** pointing back to Pomerium. That is, `https://${AUTHENTICATE_SERVICE_URL}/oauth2/callback`
2. Generate a **[Client ID]** and **[Client Secret]**.
3. Configure Pomerium to use the **[Client ID]** and **[Client Secret]** keys.
## Azure Active Directory
If you plan on allowing users to log in using a Microsoft Azure Active Directory account, either from your company or from external directories, you must register your application through the Microsoft Azure portal. If you don't have a Microsoft Azure account, you can [signup](https://azure.microsoft.com/en-us/free) for free.
You can access the Azure management portal from your Microsoft service, or visit <https://portal.azure.com> and sign in to Azure using the global administrator account used to create the Office 365 organization.
::: tip
There is no way to create an application that integrates with Microsoft Azure AD without having **your own** Microsoft Azure AD instance.
:::
If you have an Office 365 account, you can use the account's Azure AD instance instead of creating a new one. To find your Office 365 account's Azure AD instance:
1. [Sign in](https://portal.office.com) to Office 365.
2. Navigate to the [Office 365 Admin Center](https://portal.office.com/adminportal/home#/homepage).
3. Open the **Admin centers** menu drawer located in the left menu.
4. Click on **Azure AD**.
This will bring you to the admin center of the Azure AD instance backing your Office 365 account.
### Create a new application
Login to Microsoft Azure and choose **Azure Active Directory** from the sidebar.
![Select Active Directory](./microsoft/azure-dashboard.png)
Then under **MANAGE**, select **App registrations**.
![Select App registrations](./microsoft/azure-app-registrations.png)
Then click on the **+ ADD** button to add a new application.
Enter a name for the application, select **Web app/API** as the **Application Type**, and for **Sign-on URL** enter your application URL.
![Create application form](./microsoft/azure-create-application.png)
Next you will need to create a key which will be used as the **[Client Secret]** in Pomerium's configuration settings. Click on **Keys** from the **Settings** menu.
Enter a name for the key and choose the desired duration.
::: tip
If you choose an expiring key, make sure to record the expiration date in your calendar, as you will need to renew the key (get a new one) before that day in order to ensure users don't experience a service interruption.
:::
Click on **Save** and the key will be displayed. **Make sure to copy the value of this key before leaving this screen**, otherwise you may need to create a new key. This value is used as the **[Client Secret]**.
![Creating a Key](./microsoft/azure-create-key.png)
Next you need to ensure that the Pomerium's Redirect URL is listed in allowed reply URLs for the created application. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app. Then click **Settings** -> **Reply URLs** and add Pomerium's redirect URL. For example, `https://authenticate.corp.beyondperimeter.com/oauth2/callback`.
![Add Reply URL](./microsoft/azure-redirect-url.png)
Next, in order to retrieve group information from Active Directory, we need to enable the necessary permissions for the [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/auth-v2-service#azure-ad-endpoint-considerations).
On the **App registrations** page, click **API permissions**. Click the **Add a permission** button and select **Microsoft Graph API**, select **Delegated permissions**. Under the **Directory** row, select the checkbox for **Group.Read.All**.
![Azure add group membership claims](./microsoft/azure-api-settings.png)
You can also optionally select **grant admin consent for all users** which will suppress the permission screen on first login for users.
The final, and most unique step to Azure AD provider, is to take note of your specific endpoint. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app.
![Application dashboard](./microsoft/azure-application-dashbaord.png)
Click on **Endpoints**
![Endpoint details](./microsoft/azure-endpoints.png)
The **OpenID Connect Metadata Document** value will form the basis for Pomerium's **Provider URL** setting.
For example if the **Azure OpenID Connect** url is:
```bash
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0/.well-known/openid-configuration`
```
**Pomerium Identity Provider URL** would be
```bash
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0
```
### Configure Pomerium
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like:
```bash
# Azure
IDP_PROVIDER="azure"
IDP_PROVIDER_URL="https://login.microsoftonline.com/{REPLACE-ME-SEE-ABOVE}/v2.0"
IDP_CLIENT_ID="REPLACE-ME"
IDP_CLIENT_SECRET="REPLACE-ME"
```
## Gitlab
:::warning
Support was removed in v0.0.3 because Gitlab does not provide callers with a user email, under any scope, to a caller unless that user has selected her email to be public. Pomerium support is blocked until [this gitlab bug](https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387) is fixed.
:::
Log in to your Gitlab account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials).
Navigate to **User Settings** then **Applications** using the left-hand menu.
On the **Applications** page, add a new application by setting the following parameters:
Field | Description
------------ | --------------------------------------------------------------------------
Name | The name of your web app
Redirect URI | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`)
Scopes | **Must** select **read_user** and **openid**
![Create New Credentials](./gitlab/gitlab-create-application.png)
1.Click **Save Application** to proceed.
Your [Client ID] and [Client Secret] will be displayed:
![Gitlab OAuth Client ID and Secret](./gitlab/gitlab-credentials.png)
Set [Client ID] and [Client Secret] in Pomerium's settings. Your [environmental variables] should look something like this.
```bash
IDP_PROVIDER="gitlab"
# NOTE!!! Provider url is optional, but should be set if you are running an on-premise instance
# defaults to : https://gitlab.com, a local copy would look something like `http://gitlab.corp.beyondperimeter.com`
IDP_PROVIDER_URL="https://gitlab.com"
IDP_CLIENT_ID="yyyy"
IDP_CLIENT_SECRET="xxxxxx"
```
When a user first uses Pomerium to login, they will be presented with an authorization screen similar to the following.
![gitlab access authorization screen](./gitlab/gitlab-verify-access.png)
## Google
Log in to your Google account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials). Navigate to **Credentials** using the left-hand menu.
![API Manager Credentials](./google/google-credentials.png)
On the **Credentials** page, click **Create credentials** and choose **OAuth [Client ID]**.
![Create New Credentials](./google/google-create-new-credentials.png)
If you don't currently have an OAuth consent page configured, google will not allow you to create credentials until this is completed, and you will likely see **this** banner on the page.
![OAuth Consent Banner](./google/google-consent-banner.png)
Click the button on the banner to go to the consent screen configuration. If all you are configuring is pomerium, you only need to fill in "Application Name" with your desired moniker, and "Authorized Domains" with the domain that pomerium will be calling google from. Afterwards, return to the credential creation page.
![OAuth Consent Configuration](./google/google-oauth-consent.png)
On the **Create [Client ID]** page, select **Web application**. In the new fields that display, set the following parameters:
Field | Description
------------------------ | --------------------------------------------------------------------------
Name | The name of your web app
Authorized redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`)
![Web App Credentials Configuration](./google/google-create-client-id-config.png)
Click **Create** to proceed. The [Client ID] and [Client Secret] settings will be displayed for later configuration with Pomerium.
![OAuth Client ID and Secret](./google/google-oauth-client-info.png)
In order to have Pomerium validate group membership, we'll also need to configure a [service account](https://console.cloud.google.com/iam-admin/serviceaccounts) with [G-suite domain-wide delegation](https://developers.google.com/admin-sdk/directory/v1/guides/delegation) enabled.
1. Open the [Service accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) page.
2. If prompted, select a project.
3. Click **Create service** account. In the Create service account window, type a name for the service account, and select Furnish a new private key and Enable Google Apps Domain-wide Delegation.
4. Then click **Save**.
![Google create service account](./google/google-create-sa.png)
Then, you'll need to manually open an editor and add an `impersonate_user` field to the downloaded public/private key file. In this case, we'd be impersonating the admin account `user@pomerium.io`.
::: warning
[Google requires](https://stackoverflow.com/questions/48585700/is-it-possible-to-call-apis-from-service-account-without-acting-on-behalf-of-a-u/48601364#48601364) that service accounts act on behalf of another user. You MUST add the `impersonate_user` field to your json key file.
:::
```json
{
"type": "service_account",
"client_id": "109818058799274859509",
...
"impersonate_user": "user@pomerium.io"
...
}
```
The base64 encoded contents of this public/private key pair json file will used for the value of the `IDP_SERVICE_ACCOUNT` configuration setting.
Next we'll delegate G-suite group membership access to the service account we just created .
1. Go to your G Suite domain's [Admin console](http://admin.google.com/).
2. Select **Security** from the list of controls. If you don't see Security listed, select More controls 1\. from the gray bar at the bottom of the page, then select Security from the list of controls.
3. Select **Advanced settings** from the list of options.
4. Select **Manage API client** access in the Authentication section.
5. In the **Client name** field enter the service account's **Client ID**.
* (Be sure this is the client id of the service account, and not the oauth client id)
6. In the **One or More API Scopes** field enter the following list of scopes: `https://www.googleapis.com/auth/admin.directory.group.readonly` `https://www.googleapis.com/auth/admin.directory.user.readonly`
7. Click the **Authorize** button.
![Google create service account](./google/google-gsuite-add-scopes.png)
Your [environmental variables] should look something like this.
```bash
IDP_PROVIDER="google"
IDP_PROVIDER_URL="https://accounts.google.com"
IDP_CLIENT_ID="yyyy.apps.googleusercontent.com"
IDP_CLIENT_SECRET="xxxxxx"
IDP_SERVICE_ACCOUNT="zzzz" # output of `cat service-account-key.json | base64`
```
## Okta
[Log in to your Okta account](https://login.okta.com) and head to your Okta dashboard. Select **Applications** on the top menu. On the Applications page, click the **Add Application** button to create a new app.
![Okta Applications Dashboard](./okta/okta-app-dashboard.png)
On the **Create New Application** page, select the **Web** for your application.
![Okta Create Application Select Platform](./okta/okta-create-app-platform.png)
Next, provide the following information for your application settings:
Field | Description
---------------------------- | ---------------------------------------------------------------------------
Name | The name of your application.
Base URIs (optional) | The domain(s) of your application.
Login redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`).
Group assignments (optional) | The user groups that can sign in to this application.
Grant type allowed | **You must enable Refresh Token.**
![Okta Create Application Settings](./okta/okta-create-app-settings.png)
Click **Done** to proceed. You'll be taken to the **General** page of your app.
Go to the **General** page of your app and scroll down to the **Client Credentials** section. This section contains the **[Client ID]** and **[Client Secret]** to be used in the next step.
![Okta Client ID and Secret](./okta/okta-client-id-and-secret.png)
Next, we'll configure Okta to pass along a custom OpenID Connect claim to establish group membership. To do so, click the **API** menu item, and select **Authorization Servers**.
![Okta authorization servers](./okta/okta-authorization-servers.png)
Select your desired authorization server and navigate to the **claims tab**. Click **Add Claim** and configure the group claim for **ID Token** as follows.
![Okta configure group claim](./okta/okta-configure-groups-claim.png)
Field | Value
--------------------- | ---------------------
Name | groups
Include in token type | **ID Token**, Always.
Value Type | Groups
Filter | Matches regex `.*`
Include in | Any scope
Add an another, almost identical, claim but this time for **Access Token**.
Field | Value
--------------------- | -------------------------
Name | groups
Include in token type | **Access Token**, Always.
Value Type | Groups
Filter | Matches regex `.*`
Include in | Any scope
![Okta list group claims](./okta/okta-list-groups-claim.png)
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like this.
```bash
IDP_PROVIDER="okta"
IDP_PROVIDER_URL="https://dev-108295-admin.oktapreview.com/"
IDP_CLIENT_ID="0oairksnr0C0fEJ7l0h7"
IDP_CLIENT_SECRET="xxxxxx"
```
## OneLogin
Log in to your [OneLogin](https://www.onelogin.com/) account and head to the dashboard.
Click **Apps** on the top menu. Select the **Add apps** menu item.
![One Login Add a New App](./one-login/one-login-add-app.png)
On the **Find Application** page, search for **openid**. Select **Openid Connect** by OneLogin, Inc.
![One Login Add a New App](./one-login/one-login-add-open-id.png)
On the App Configuration page, **name the app** and **select a logo**. Select **Save**.
![One Login select logo](./one-login/one-login-select-logo.png)
Next, set set the **Redirect URI's** setting to be Pomerium's redirect url `https://${AUTHENTICATE_SERVICE_URL}/oauth2/callback`.
![One Login set callback url](./one-login/one-login-callback-url.png)
Go to the **SSO** page. This section contains the **[Client ID]** and **[Client Secret]** you'll use to configure Pomerium.
Set the application type to **Web** and the token endpoint to be **POST**.
Under **Token Timeout settings** set **Refresh Token** to 60 minutes (or whatever value makes sense for your organization). Note, however, if you don't enable refresh tokens the user will be prompted to authenticate whenever the access token expires which can result in a poor user experience.
![One Login SSO settings](./one-login/one-login-sso-settings.png)
[OneLogin's OIDC implementation](https://developers.onelogin.com/openid-connect/scopes) supports the `groups` which can return either the user's group or role which can be used within pomerium to enforced group-based ACL policy.
To return the user's Active Directory field, configure the group to return `member_of`. In the Default if no value field, select **User Roles** and Select **Semicolon Delimited** in the adjacent field. **Select Save**
![OneLogin set role](./one-login/one-login-oidc-params.png)
**Alternatively**, groups can return the _roles_ a user is assigned. In the Default if no value field, select **User Roles** and Select **Semicolon Delimited** in the adjacent field. **Select Save**
![OneLogin set role](./one-login/one-login-oidc-groups-param.png)
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like this.
```bash
IDP_PROVIDER="onelogin"
IDP_PROVIDER_URL="https://openid-connect.onelogin.com/oidc"
IDP_CLIENT_ID="9e613ce0-1622-0137-452d-0a93c31f8392142934"
IDP_CLIENT_SECRET="3e86ef0cc21b6dcf10c1d91e032568617d37e9fe1609ffd8042d3c25a560c36c"
```
After reloading Pomerium, you should be able to see any login events from your OneLogin events dashboard.
![One Login Events Dashboard](./one-login/one-login-events.png)
[client id]: ./config-reference.html#identity-provider-client-id
[client secret]: ./config-reference.html#identity-provider-client-secret
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
[oauth2]: https://oauth.net/2/
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect

View file

@ -0,0 +1,111 @@
---
title: Azure AD
lang: en-US
sidebarDepth: 0
meta:
- name: keywords
content: azure active-directory active directory ad microsoft
---
# Azure Active Directory
If you plan on allowing users to log in using a Microsoft Azure Active Directory account, either from your company or from external directories, you must register your application through the Microsoft Azure portal. If you don't have a Microsoft Azure account, you can [signup](https://azure.microsoft.com/en-us/free) for free.
You can access the Azure management portal from your Microsoft service, or visit <https://portal.azure.com> and sign in to Azure using the global administrator account used to create the Office 365 organization.
::: tip
There is no way to create an application that integrates with Microsoft Azure AD without having **your own** Microsoft Azure AD instance.
:::
If you have an Office 365 account, you can use the account's Azure AD instance instead of creating a new one. To find your Office 365 account's Azure AD instance:
1. [Sign in](https://portal.office.com) to Office 365.
2. Navigate to the [Office 365 Admin Center](https://portal.office.com/adminportal/home#/homepage).
3. Open the **Admin centers** menu drawer located in the left menu.
4. Click on **Azure AD**.
This will bring you to the admin center of the Azure AD instance backing your Office 365 account.
**Create a new application**
Login to Microsoft Azure and choose **Azure Active Directory** from the sidebar.
![Select Active Directory](./img/azure-dashboard.png)
Then under **MANAGE**, select **App registrations**.
![Select App registrations](./img/azure-app-registrations.png)
Then click on the **+ ADD** button to add a new application.
Enter a name for the application, select **Web app/API** as the **Application Type**, and for **Sign-on URL** enter your application URL.
![Create application form](./img/azure-create-application.png)
Next you will need to create a key which will be used as the **[Client Secret]** in Pomerium's configuration settings. Click on **Keys** from the **Settings** menu.
Enter a name for the key and choose the desired duration.
::: tip
If you choose an expiring key, make sure to record the expiration date in your calendar, as you will need to renew the key (get a new one) before that day in order to ensure users don't experience a service interruption.
:::
Click on **Save** and the key will be displayed. **Make sure to copy the value of this key before leaving this screen**, otherwise you may need to create a new key. This value is used as the **[Client Secret]**.
![Creating a Key](./img/azure-create-key.png)
Next you need to ensure that the Pomerium's Redirect URL is listed in allowed reply URLs for the created application. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app. Then click **Settings** -> **Reply URLs** and add Pomerium's redirect URL. For example, `https://authenticate.corp.beyondperimeter.com/oauth2/callback`.
![Add Reply URL](./img/azure-redirect-url.png)
Next, in order to retrieve group information from Active Directory, we need to enable the necessary permissions for the [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/auth-v2-service#azure-ad-endpoint-considerations).
On the **App registrations** page, click **API permissions**. Click the **Add a permission** button and select **Microsoft Graph API**, select **Delegated permissions**. Under the **Directory** row, select the checkbox for **Group.Read.All**.
![Azure add group membership claims](./img/azure-api-settings.png)
You can also optionally select **grant admin consent for all users** which will suppress the permission screen on first login for users.
The final, and most unique step to Azure AD provider, is to take note of your specific endpoint. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app.
![Application dashboard](./img/azure-application-dashbaord.png)
Click on **Endpoints**
![Endpoint details](./img/azure-endpoints.png)
The **OpenID Connect Metadata Document** value will form the basis for Pomerium's **Provider URL** setting.
For example if the **Azure OpenID Connect** url is:
```bash
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0/.well-known/openid-configuration`
```
**Pomerium Identity Provider URL** would be
```bash
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0
```
**Configure Pomerium**
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like:
```bash
# Azure
IDP_PROVIDER="azure"
IDP_PROVIDER_URL="https://login.microsoftonline.com/{REPLACE-ME-SEE-ABOVE}/v2.0"
IDP_CLIENT_ID="REPLACE-ME"
IDP_CLIENT_SECRET="REPLACE-ME"
```
[client id]: ../reference/reference.md#identity-provider-client-id
[client secret]: ../reference/reference.md#identity-provider-client-secret
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
[oauth2]: https://oauth.net/2/
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect

View file

@ -0,0 +1,48 @@
# Gitlab
:::warning
Support was removed in v0.0.3 because Gitlab does not provide callers with a user email, under any scope, to a caller unless that user has selected her email to be public. Pomerium support is blocked until [this gitlab bug](https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387) is fixed.
:::
Log in to your Gitlab account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials).
Navigate to **User Settings** then **Applications** using the left-hand menu.
On the **Applications** page, add a new application by setting the following parameters:
| Field | Description |
| ------------ | -------------------------------------------------------------------------- |
| Name | The name of your web app |
| Redirect URI | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`) |
| Scopes | **Must** select **read_user** and **openid** |
![Create New Credentials](./img/gitlab-create-application.png)
1.Click **Save Application** to proceed.
Your [Client ID] and [Client Secret] will be displayed:
![Gitlab OAuth Client ID and Secret](./img/gitlab-credentials.png)
Set [Client ID] and [Client Secret] in Pomerium's settings. Your [environmental variables] should look something like this.
```bash
IDP_PROVIDER="gitlab"
# NOTE!!! Provider url is optional, but should be set if you are running an on-premise instance
# defaults to : https://gitlab.com, a local copy would look something like `http://gitlab.corp.beyondperimeter.com`
IDP_PROVIDER_URL="https://gitlab.com"
IDP_CLIENT_ID="yyyy"
IDP_CLIENT_SECRET="xxxxxx"
```
When a user first uses Pomerium to login, they will be presented with an authorization screen similar to the following.
![gitlab access authorization screen](./img/gitlab-verify-access.png)
[client id]: ../reference/reference.md#identity-provider-client-id
[client secret]: ../reference/reference.md#identity-provider-client-secret
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
[oauth2]: https://oauth.net/2/
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect

View file

@ -0,0 +1,87 @@
# Google
Log in to your Google account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials). Navigate to **Credentials** using the left-hand menu.
![API Manager Credentials](./img/google-credentials.png)
On the **Credentials** page, click **Create credentials** and choose **OAuth [Client ID]**.
![Create New Credentials](./img/google-create-new-credentials.png)
If you don't currently have an OAuth consent page configured, google will not allow you to create credentials until this is completed, and you will likely see **this** banner on the page.
![OAuth Consent Banner](./img/google-consent-banner.png)
Click the button on the banner to go to the consent screen configuration. If all you are configuring is pomerium, you only need to fill in "Application Name" with your desired moniker, and "Authorized Domains" with the domain that pomerium will be calling google from. Afterwards, return to the credential creation page.
![OAuth Consent Configuration](./img/google-oauth-consent.png)
On the **Create [Client ID]** page, select **Web application**. In the new fields that display, set the following parameters:
| Field | Description |
| ------------------------ | -------------------------------------------------------------------------- |
| Name | The name of your web app |
| Authorized redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`) |
![Web App Credentials Configuration](./img/google-create-client-id-config.png)
Click **Create** to proceed. The [Client ID] and [Client Secret] settings will be displayed for later configuration with Pomerium.
![OAuth Client ID and Secret](./img/google-oauth-client-info.png)
In order to have Pomerium validate group membership, we'll also need to configure a [service account](https://console.cloud.google.com/iam-admin/serviceaccounts) with [G-suite domain-wide delegation](https://developers.google.com/admin-sdk/directory/v1/guides/delegation) enabled.
1. Open the [Service accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) page.
2. If prompted, select a project.
3. Click **Create service** account. In the Create service account window, type a name for the service account, and select Furnish a new private key and Enable Google Apps Domain-wide Delegation.
4. Then click **Save**.
![Google create service account](./img/google-create-sa.png)
Then, you'll need to manually open an editor and add an `impersonate_user` field to the downloaded public/private key file. In this case, we'd be impersonating the admin account `user@pomerium.io`.
::: warning
[Google requires](https://stackoverflow.com/questions/48585700/is-it-possible-to-call-apis-from-service-account-without-acting-on-behalf-of-a-u/48601364#48601364) that service accounts act on behalf of another user. You MUST add the `impersonate_user` field to your json key file.
:::
```json
{
"type": "service_account",
"client_id": "109818058799274859509",
...
"impersonate_user": "user@pomerium.io"
...
}
```
The base64 encoded contents of this public/private key pair json file will used for the value of the `IDP_SERVICE_ACCOUNT` configuration setting.
Next we'll delegate G-suite group membership access to the service account we just created .
1. Go to your G Suite domain's [Admin console](http://admin.google.com/).
2. Select **Security** from the list of controls. If you don't see Security listed, select More controls 1\. from the gray bar at the bottom of the page, then select Security from the list of controls.
3. Select **Advanced settings** from the list of options.
4. Select **Manage API client** access in the Authentication section.
5. In the **Client name** field enter the service account's **Client ID**.
6. In the **One or More API Scopes** field enter the following list of scopes: `https://www.googleapis.com/auth/admin.directory.group.readonly` `https://www.googleapis.com/auth/admin.directory.user.readonly`
7. Click the **Authorize** button.
![Google create service account](./img/google-gsuite-add-scopes.png)
Your [environmental variables] should look something like this.
```bash
IDP_PROVIDER="google"
IDP_PROVIDER_URL="https://accounts.google.com"
IDP_CLIENT_ID="yyyy.apps.googleusercontent.com"
IDP_CLIENT_SECRET="xxxxxx"
IDP_SERVICE_ACCOUNT="zzzz" # output of `cat service-account-key.json | base64`
```
[client id]: ../reference/reference.md#identity-provider-client-id
[client secret]: ../reference/reference.md#identity-provider-client-secret
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
[oauth2]: https://oauth.net/2/
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View file

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Some files were not shown because too many files have changed in this diff Show more