diff --git a/.env.example b/.env.example index b662b7961..989a285e3 100644 --- a/.env.example +++ b/.env.example @@ -47,7 +47,12 @@ SMTP_PASSWORD= # Integration # Optional only if integration is used -OAUTH_CLIENT_SECRET_HEROKU= +CLIENT_ID_HEROKU= +CLIENT_ID_VERCEL= +CLIENT_ID_NETLIFY= +CLIENT_SECRET_HEROKU= +CLIENT_SECRET_VERCEL= +CLIENT_SECRET_NETLIFY= # Sentry (optional) for monitoring errors SENTRY_DSN= diff --git a/.github/workflows/release_docker_k8_operator.yaml b/.github/workflows/release_docker_k8_operator.yaml new file mode 100644 index 000000000..01aa3b625 --- /dev/null +++ b/.github/workflows/release_docker_k8_operator.yaml @@ -0,0 +1,38 @@ +name: Release Docker image for K8 operator +on: [workflow_dispatch] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: 🔧 Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: 🔧 Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: 🐋 Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + context: k8-operator + push: true + platforms: linux/amd64,linux/arm64 + tags: infisical/kubernetes-operator:latest + + - uses: actions/setup-go@v2 + + - name: Upload CRD manifest + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: dist/install-secrets-operator.yaml + tag: ${{ github.ref }} \ No newline at end of file diff --git a/backend/environment.d.ts b/backend/environment.d.ts index 33827fb2b..853f52e5b 100644 --- a/backend/environment.d.ts +++ b/backend/environment.d.ts @@ -14,8 +14,12 @@ declare global { JWT_SIGNUP_SECRET: string; MONGO_URL: string; NODE_ENV: 'development' | 'staging' | 'testing' | 'production'; - OAUTH_CLIENT_SECRET_HEROKU: string; - OAUTH_TOKEN_URL_HEROKU: string; + CLIENT_ID_HEROKU: string; + CLIENT_ID_VERCEL: string; + CLIENT_ID_NETLIFY: string; + CLIENT_SECRET_HEROKU: string; + CLIENT_SECRET_VERCEL: string; + CLIENT_SECRET_NETLIFY: string; POSTHOG_HOST: string; POSTHOG_PROJECT_API_KEY: string; PRIVATE_KEY: string; diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 833fd5642..aa02d08a1 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,8 @@ const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m'; const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!; const MONGO_URL = process.env.MONGO_URL!; const NODE_ENV = process.env.NODE_ENV! || 'production'; -const OAUTH_CLIENT_SECRET_HEROKU = process.env.OAUTH_CLIENT_SECRET_HEROKU!; +const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!; +const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!; const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!; const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!; const CLIENT_ID_GITHUB = @@ -54,10 +55,11 @@ export { JWT_SIGNUP_SECRET, MONGO_URL, NODE_ENV, - OAUTH_CLIENT_SECRET_HEROKU, + CLIENT_ID_HEROKU, CLIENT_ID_VERCEL, CLIENT_ID_NETLIFY, CLIENT_ID_GITHUB, + CLIENT_SECRET_HEROKU, CLIENT_SECRET_VERCEL, CLIENT_SECRET_NETLIFY, CLIENT_SECRET_GITHUB, diff --git a/backend/src/controllers/integrationAuthController.ts b/backend/src/controllers/integrationAuthController.ts index fc2767e0a..c242c239a 100644 --- a/backend/src/controllers/integrationAuthController.ts +++ b/backend/src/controllers/integrationAuthController.ts @@ -3,10 +3,19 @@ import * as Sentry from '@sentry/node'; import axios from 'axios'; import { readFileSync } from 'fs'; import { IntegrationAuth, Integration } from '../models'; -import { INTEGRATION_SET, ENV_DEV } from '../variables'; +import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../variables'; import { IntegrationService } from '../services'; import { getApps, revokeAccess } from '../integrations'; +export const getIntegrationOptions = async ( + req: Request, + res: Response +) => { + return res.status(200).send({ + integrationOptions: INTEGRATION_OPTIONS + }); +} + /** * Perform OAuth2 code-token exchange as part of integration [integration] for workspace with id [workspaceId] * @param req diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 0ddc2d20c..3b7526200 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -11,9 +11,9 @@ import { INTEGRATION_GITHUB_TOKEN_URL, ACTION_PUSH_TO_HEROKU } from '../variables'; + import { SITE_URL, - OAUTH_CLIENT_SECRET_HEROKU, CLIENT_ID_VERCEL, CLIENT_ID_NETLIFY, CLIENT_ID_GITHUB, @@ -117,6 +117,7 @@ const exchangeCode = async ({ * @returns {String} obj2.refreshToken - refresh token for Heroku API * @returns {Date} obj2.accessExpiresAt - date of expiration for access token */ +<<<<<<< HEAD const exchangeCodeHeroku = async ({ code }: { code: string }) => { let res: ExchangeCodeHerokuResponse; const accessExpiresAt = new Date(); @@ -145,6 +146,40 @@ const exchangeCodeHeroku = async ({ code }: { code: string }) => { accessExpiresAt }; }; +======= +const exchangeCodeHeroku = async ({ + code +}: { + code: string; +}) => { + let res: ExchangeCodeHerokuResponse; + let accessExpiresAt = new Date(); + try { + res = (await axios.post( + INTEGRATION_HEROKU_TOKEN_URL, + new URLSearchParams({ + grant_type: 'authorization_code', + code: code, + client_secret: CLIENT_SECRET_HEROKU + } as any) + )).data; + + accessExpiresAt.setSeconds( + accessExpiresAt.getSeconds() + res.expires_in + ); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed OAuth2 code-token exchange with Heroku'); + } + + return ({ + accessToken: res.access_token, + refreshToken: res.refresh_token, + accessExpiresAt + }); +} +>>>>>>> 5444382d5ae1fabf1107434a856b58b9f09c67f6 /** * Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel diff --git a/backend/src/integrations/refresh.ts b/backend/src/integrations/refresh.ts index cbe7726ea..382d3ca24 100644 --- a/backend/src/integrations/refresh.ts +++ b/backend/src/integrations/refresh.ts @@ -1,8 +1,12 @@ import axios from 'axios'; import * as Sentry from '@sentry/node'; import { INTEGRATION_HEROKU } from '../variables'; -import { OAUTH_CLIENT_SECRET_HEROKU } from '../config'; -import { INTEGRATION_HEROKU_TOKEN_URL } from '../variables'; +import { + CLIENT_SECRET_HEROKU +} from '../config'; +import { + INTEGRATION_HEROKU_TOKEN_URL +} from '../variables'; /** * Return new access token by exchanging refresh token [refreshToken] for integration @@ -48,6 +52,7 @@ const exchangeRefreshHeroku = async ({ }: { refreshToken: string; }) => { +<<<<<<< HEAD let accessToken; try { const res = await axios.post( @@ -58,6 +63,18 @@ const exchangeRefreshHeroku = async ({ client_secret: OAUTH_CLIENT_SECRET_HEROKU } as any) ); +======= + let accessToken; + try { + const res = await axios.post( + INTEGRATION_HEROKU_TOKEN_URL, + new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: refreshToken, + client_secret: CLIENT_SECRET_HEROKU + } as any) + ); +>>>>>>> 5444382d5ae1fabf1107434a856b58b9f09c67f6 accessToken = res.data.access_token; } catch (err) { diff --git a/backend/src/middleware/requireIntegrationAuthorizationAuth.ts b/backend/src/middleware/requireIntegrationAuthorizationAuth.ts index 0bf552654..ed44ffec5 100644 --- a/backend/src/middleware/requireIntegrationAuthorizationAuth.ts +++ b/backend/src/middleware/requireIntegrationAuthorizationAuth.ts @@ -10,14 +10,16 @@ import { validateMembership } from '../helpers/membership'; * @param {Object} obj * @param {String[]} obj.acceptedRoles - accepted workspace roles * @param {String[]} obj.acceptedStatuses - accepted workspace statuses - * @param {Boolean} obj.attachRefresh - whether or not to decrypt and attach integration authorization refresh token onto request + * @param {Boolean} obj.attachAccessToken - whether or not to decrypt and attach integration authorization access token onto request */ const requireIntegrationAuthorizationAuth = ({ acceptedRoles, - acceptedStatuses + acceptedStatuses, + attachAccessToken = true }: { acceptedRoles: string[]; acceptedStatuses: string[]; + attachAccessToken?: boolean; }) => { return async (req: Request, res: Response, next: NextFunction) => { try { @@ -41,9 +43,11 @@ const requireIntegrationAuthorizationAuth = ({ }); req.integrationAuth = integrationAuth; - req.accessToken = await IntegrationService.getIntegrationAuthAccess({ - integrationAuthId: integrationAuth._id.toString() - }); + if (attachAccessToken) { + req.accessToken = await IntegrationService.getIntegrationAuthAccess({ + integrationAuthId: integrationAuth._id.toString() + }); + } return next(); } catch (err) { diff --git a/backend/src/routes/integrationAuth.ts b/backend/src/routes/integrationAuth.ts index 650221f82..ef80a2dcc 100644 --- a/backend/src/routes/integrationAuth.ts +++ b/backend/src/routes/integrationAuth.ts @@ -10,6 +10,12 @@ import { import { ADMIN, MEMBER, GRANTED } from '../variables'; import { integrationAuthController } from '../controllers'; +router.get( + '/integration-options', + requireAuth, + integrationAuthController.getIntegrationOptions +); + router.post( '/oauth-token', requireAuth, @@ -42,7 +48,8 @@ router.delete( requireAuth, requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], - acceptedStatuses: [GRANTED] + acceptedStatuses: [GRANTED], + attachAccessToken: false }), param('integrationAuthId'), validateRequest, diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index 0824b49bf..c69ed8176 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -19,7 +19,8 @@ import { INTEGRATION_HEROKU_API_URL, INTEGRATION_VERCEL_API_URL, INTEGRATION_NETLIFY_API_URL, - INTEGRATION_GITHUB_API_URL + INTEGRATION_GITHUB_API_URL, + INTEGRATION_OPTIONS } from './integration'; import { OWNER, @@ -68,5 +69,6 @@ export { INTEGRATION_GITHUB_API_URL, EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS, - ACTION_PUSH_TO_HEROKU + ACTION_PUSH_TO_HEROKU, + INTEGRATION_OPTIONS }; diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index d5483904a..f67b57b59 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -1,3 +1,8 @@ +import { + CLIENT_ID_HEROKU, + CLIENT_ID_NETLIFY +} from '../config'; + // integrations const INTEGRATION_HEROKU = 'heroku'; const INTEGRATION_VERCEL = 'vercel'; @@ -27,6 +32,81 @@ const INTEGRATION_VERCEL_API_URL = 'https://api.vercel.com'; const INTEGRATION_NETLIFY_API_URL = 'https://api.netlify.com'; const INTEGRATION_GITHUB_API_URL = ' https://api.github.com/'; +const INTEGRATION_OPTIONS = [ + { + name: 'Heroku', + slug: 'heroku', + image: 'Heroku', + isAvailable: true, + type: 'oauth2', + clientId: CLIENT_ID_HEROKU, + docsLink: '' + }, + { + name: 'Vercel', + slug: 'vercel', + image: 'Vercel', + isAvailable: true, + type: 'vercel', + clientId: '', + docsLink: '' + }, + { + name: 'Netlify', + slug: 'netlify', + image: 'Netlify', + isAvailable: true, + type: 'oauth2', + clientId: CLIENT_ID_NETLIFY, + docsLink: '' + }, + { + name: 'Google Cloud Platform', + slug: 'gcp', + image: 'Google Cloud Platform', + isAvailable: false, + type: '', + clientId: '', + docsLink: '' + }, + { + name: 'Amazon Web Services', + slug: 'aws', + image: 'Amazon Web Services', + isAvailable: false, + type: '', + clientId: '', + docsLink: '' + }, + { + name: 'Microsoft Azure', + slug: 'azure', + image: 'Microsoft Azure', + isAvailable: false, + type: '', + clientId: '', + docsLink: '' + }, + { + name: 'Travis CI', + slug: 'travisci', + image: 'Travis CI', + isAvailable: false, + type: '', + clientId: '', + docsLink: '' + }, + { + name: 'Circle CI', + slug: 'circleci', + image: 'Circle CI', + isAvailable: false, + type: '', + clientId: '', + docsLink: '' + } +] + export { INTEGRATION_HEROKU, INTEGRATION_VERCEL, @@ -41,5 +121,6 @@ export { INTEGRATION_HEROKU_API_URL, INTEGRATION_VERCEL_API_URL, INTEGRATION_NETLIFY_API_URL, - INTEGRATION_GITHUB_API_URL + INTEGRATION_GITHUB_API_URL, + INTEGRATION_OPTIONS }; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 623462d5b..15a200783 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -51,6 +51,7 @@ services: env_file: .env environment: - NEXT_PUBLIC_ENV=development + - INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED} - NEXT_PUBLIC_STRIPE_PRODUCT_PRO=${STRIPE_PRODUCT_PRO} - NEXT_PUBLIC_STRIPE_PRODUCT_STARTER=${STRIPE_PRODUCT_STARTER} networks: diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index a55efbebc..9c6697df5 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -28,6 +28,9 @@ Configuring Infisical requires setting some environment variables. There is a fi | `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` | | `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` | | `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` | -| `OAUTH_CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` | -| `OAUTH_TOKEN_URL_HEROKU` | OAuth token URL for Heroku integration | `None` | +| `CLIENT_ID_VERCEL` | OAuth client id for Vercel integration | `None` | +| `CLIENT_ID_NETLIFY` | OAuth client id for Netlify integration | `None` | +| `CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` | +| `CLIENT_SECRET_VERCEL` | OAuth client secret for Vercel integration | `None` | +| `CLIENT_SECRET_NETLIFY` | OAuth client secret for Netlify integration | `None` | | `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` | diff --git a/frontend/components/integrations/CloudIntegration.tsx b/frontend/components/integrations/CloudIntegration.tsx index b9bbb08c8..f64268dba 100644 --- a/frontend/components/integrations/CloudIntegration.tsx +++ b/frontend/components/integrations/CloudIntegration.tsx @@ -87,6 +87,7 @@ const CloudIntegration = ({ ) .map((authorization) => authorization._id)[0], }); + router.reload(); }} className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200" diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx index 8a323336e..a42fa5bcf 100644 --- a/frontend/components/integrations/Integration.tsx +++ b/frontend/components/integrations/Integration.tsx @@ -17,8 +17,6 @@ import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps" import Button from "~/components/basic/buttons/Button"; import ListBox from "~/components/basic/Listbox"; -// TODO: optimize laggy dropdown for app options - interface Integration { app?: string; environment: string; @@ -174,6 +172,7 @@ const Integration = ({