Merge pull request #23 from m1k1o/policies

Policies generator
This commit is contained in:
Miroslav Šedivý
2021-08-15 14:38:59 +02:00
committed by GitHub
17 changed files with 1041 additions and 11 deletions
+41
View File
@@ -234,6 +234,9 @@ components:
items:
type: string
example: m1k1o/neko:latest
storage_enabled:
type: boolean
example: true
RoomEntry:
type: object
@@ -330,6 +333,8 @@ components:
type: array
items:
$ref: '#/components/schemas/RoomMount'
browser_policy:
$ref: '#/components/schemas/BrowserPolicy'
RoomStats:
type: object
@@ -360,3 +365,39 @@ components:
muted:
type: boolean
example: false
BrowserPolicy:
type: object
properties:
type:
type: string
enum: [ firefox, chromium ]
path:
type: string
example: /usr/lib/firefox/distribution/policies.json
content:
$ref: '#/components/schemas/BrowserPolicyContent'
BrowserPolicyContent:
type: object
properties:
extensions:
type: array
items:
$ref: '#/components/schemas/BrowserPolicyExtension'
developer_tools:
type: boolean
example: true
persistent_data:
type: boolean
example: false
BrowserPolicyExtension:
type: object
properties:
id:
type: string
example: uBlock0@raymondhill.net
url:
type: string
example: https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi
+1 -1
View File
@@ -1 +1 @@
5.2.0-SNAPSHOT
5.2.1-SNAPSHOT
+91
View File
@@ -21,6 +21,85 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';
/**
*
* @export
* @interface BrowserPolicy
*/
export interface BrowserPolicy {
/**
*
* @type {string}
* @memberof BrowserPolicy
*/
type?: BrowserPolicyTypeEnum;
/**
*
* @type {string}
* @memberof BrowserPolicy
*/
path?: string;
/**
*
* @type {BrowserPolicyContent}
* @memberof BrowserPolicy
*/
content?: BrowserPolicyContent;
}
/**
* @export
* @enum {string}
*/
export enum BrowserPolicyTypeEnum {
firefox = 'firefox',
chromium = 'chromium'
}
/**
*
* @export
* @interface BrowserPolicyContent
*/
export interface BrowserPolicyContent {
/**
*
* @type {Array<BrowserPolicyExtension>}
* @memberof BrowserPolicyContent
*/
extensions?: Array<BrowserPolicyExtension>;
/**
*
* @type {boolean}
* @memberof BrowserPolicyContent
*/
developer_tools?: boolean;
/**
*
* @type {boolean}
* @memberof BrowserPolicyContent
*/
persistent_data?: boolean;
}
/**
*
* @export
* @interface BrowserPolicyExtension
*/
export interface BrowserPolicyExtension {
/**
*
* @type {string}
* @memberof BrowserPolicyExtension
*/
id?: string;
/**
*
* @type {string}
* @memberof BrowserPolicyExtension
*/
url?: string;
}
/**
*
* @export
@@ -245,6 +324,12 @@ export interface RoomSettings {
* @memberof RoomSettings
*/
mounts?: Array<RoomMount>;
/**
*
* @type {BrowserPolicy}
* @memberof RoomSettings
*/
browser_policy?: BrowserPolicy;
}
/**
*
@@ -289,6 +374,12 @@ export interface RoomsConfig {
* @memberof RoomsConfig
*/
neko_images?: Array<string>;
/**
*
* @type {boolean}
* @memberof RoomsConfig
*/
storage_enabled?: boolean;
}
/**
+52 -2
View File
@@ -150,17 +150,45 @@
<v-simple-table>
<template v-slot:default>
<tbody>
<tr v-for="({ host_path, container_path }, index) in settings.mounts" :key="index"><td style="width:50%;">{{ host_path }}</td><td>{{ container_path }}</td></tr>
<tr v-for="({ type, host_path, container_path }, index) in settings.mounts" :key="index"><td style="width:50%;">{{ host_path }} <v-chip small>{{ type }}</v-chip></td><td>{{ container_path }}</td></tr>
</tbody>
</template>
</v-simple-table>
<div class="my-3 headline">Browser policy</div>
<v-simple-table v-if="settings.browser_policy">
<template v-slot:default>
<tbody>
<tr><th> Type </th><td>{{ settings.browser_policy.type }}</td></tr>
<tr><th> Path </th><td>{{ settings.browser_policy.path }}</td></tr>
<tr><th> Content </th><td>
<v-simple-table>
<template v-slot:default>
<tbody>
<tr><th> Extensions </th><td>{{ BrowserExtensions(settings.browser_policy.content.extensions).join(', ') }}</td></tr>
<tr><th> Developer tools </th><td>{{ settings.browser_policy.content.developer_tools }}</td></tr>
<tr><th> Persistent data </th><td>{{ settings.browser_policy.content.persistent_data }}</td></tr>
</tbody>
</template>
</v-simple-table>
</td></tr>
</tbody>
</template>
</v-simple-table>
<div v-else> none </div>
</template>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
import { RoomStats, RoomMember, RoomSettings, RoomEntry } from '@/api/index'
import {
RoomStats,
RoomMember,
RoomSettings,
RoomEntry,
} from '@/api/index'
@Component
export default class RoomInfo extends Vue {
@@ -231,5 +259,27 @@ export default class RoomInfo extends Vue {
this.statsLoading = false
}
}
get allBrowserExtensions() {
if (!this.settings?.browser_policy?.type) return {}
/*return this.$store.state.browserPolicyExtensions.map(({ text, value }: {
text: string;
value: Record<BrowserPolicyTypeEnum, BrowserPolicyExtension>;
}) => [value[this.settings.browser_policy.type], text])*/
const obj = {} as Record<string, string>
for (const { value, text } of this.$store.state.browserPolicyExtensions) {
const id = value[this.settings.browser_policy.type].id
obj[id] = text
}
return obj
}
BrowserExtensions(extensions: { id: string }[]) {
return extensions.map(({ id }: { id: string }) =>
id in this.allBrowserExtensions ? this.allBrowserExtensions[id] : id)
}
}
</script>
+129 -3
View File
@@ -208,10 +208,18 @@
<v-btn @click="delEnv(index)" icon color="red"><v-icon>mdi-close</v-icon></v-btn>
</div>
</v-row>
<v-row align="center" no-gutters class="mt-3">
<v-row align="center" no-gutters class="my-3">
<h2> Mounts </h2>
<v-btn @click="data.mounts = [ ...data.mounts, { type: 'private', host_path: '', container_path: '' }]" icon color="green"><v-icon>mdi-plus</v-icon></v-btn>
<v-btn :disabled="!storageEnabled" @click="data.mounts = [ ...data.mounts, { type: 'private', host_path: '', container_path: '' }]" icon color="green"><v-icon>mdi-plus</v-icon></v-btn>
</v-row>
<v-alert
border="left"
type="warning"
v-if="!storageEnabled"
>
<p><strong>Not available!</strong></p>
<p class="mb-0">Mounts are not available, because storage is not enabled.</p>
</v-alert>
<v-row align="center" class="mb-2" v-for="({ type, host_path, container_path }, index) in data.mounts" :key="index">
<v-col class="py-0" cols="2">
<v-select
@@ -252,6 +260,71 @@
</p>
</v-row>
</template>
<hr />
<v-row align="center" no-gutters class="mt-3">
<h2 class="my-3">
Browser policy
</h2>
<v-checkbox
v-model="browserPolicyEnabled"
:disabled="!browserPolicyConfig || !storageEnabled"
hide-details
class="shrink ml-2 mt-0"
></v-checkbox>
</v-row>
<v-alert
border="left"
type="warning"
v-if="!storageEnabled"
>
<p><strong>Not available!</strong></p>
<p class="mb-0">Browser policy is not available, because storage is not enabled.</p>
</v-alert>
<template v-else-if="browserPolicyConfig">
<v-row align="center" no-gutters class="mt-0">
<v-col>
<v-select
v-model="browserPolicyContent.extensions"
label="Extensions"
:items="browserPolicyExtensions"
multiple
:disabled="!browserPolicyEnabled"
></v-select>
</v-col>
</v-row>
<v-row align="center">
<v-col>
<v-checkbox
v-model="browserPolicyContent.developer_tools"
label="Enable developer tools"
hide-details
class="shrink ml-2 mt-0"
:disabled="!browserPolicyEnabled"
></v-checkbox>
</v-col>
<v-col>
<v-checkbox
v-model="browserPolicyContent.persistent_data"
label="Allow persistent data"
hide-details
class="shrink ml-2 mt-0"
:disabled="!browserPolicyEnabled"
></v-checkbox>
</v-col>
</v-row>
</template>
<v-alert
border="left"
type="info"
v-else
>
<p><strong>Not available!</strong></p>
<p class="mb-0">Browser policy is not available for this image.</p>
</v-alert>
</v-form>
</v-card-text>
<v-card-actions>
@@ -276,10 +349,14 @@
</template>
<script lang="ts">
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Vue, Component, Ref, Watch } from 'vue-property-decorator'
import {
RoomSettings,
BrowserPolicyContent,
BrowserPolicyExtension,
BrowserPolicyTypeEnum,
RoomMountTypeEnum,
} from '@/api/index'
export type VForm = Vue & {
@@ -305,9 +382,11 @@ export default class RoomsCreate extends Vue {
private videoPipelineEnabled = false
private audioPipelineEnabled = false
private broadcastPipelineEnabled = false
private browserPolicyEnabled = false
private loading = false
private data: RoomSettings = { ...this.$store.state.defaultRoomSettings }
private browserPolicyContent: BrowserPolicyContent = { ...this.$store.state.defaultBrowserPolicyContent }
private envList: { key: string; val: string }[] = []
// eslint-disable-next-line
@@ -340,6 +419,10 @@ export default class RoomsCreate extends Vue {
return this.$store.state.roomsConfig.neko_images
}
get storageEnabled() {
return this.$store.state.roomsConfig.storage_enabled
}
get videoCodecs() {
return this.$store.state.videoCodecs
}
@@ -369,6 +452,42 @@ export default class RoomsCreate extends Vue {
]
}
get browserPolicyConfig() {
const nekoImage = this.data.neko_image
if (!nekoImage) return undefined
return this.$store.state.browserPolicyConfig.find(({ images }: { images: string[] }): boolean => images.includes(nekoImage))
}
get browserPolicyExtensions() {
const config = this.browserPolicyConfig
if (!config) return []
return this.$store.state.browserPolicyExtensions.map(({ text, value }: {
text: string;
value: Record<BrowserPolicyTypeEnum, BrowserPolicyExtension>;
}) => {
return {
text,
value: value[config.type as BrowserPolicyTypeEnum],
}
})
}
@Watch('browserPolicyContent.persistent_data')
onPersistentDataUpdate(enabled: boolean) {
const config = this.browserPolicyConfig
if (!config) return
if (enabled) {
// eslint-disable-next-line
this.data.mounts = [ ...(this.data.mounts || []), { type: RoomMountTypeEnum.private, host_path: '/profile', container_path: config.profile }]
} else {
// eslint-disable-next-line
this.data.mounts = (this.data.mounts || []).filter(({ type, container_path }) => type != RoomMountTypeEnum.private && container_path != config.profile)
}
}
addEnv() {
Vue.set(this, 'envList', [ ...this.envList, { key: '', val: '' } ])
}
@@ -407,6 +526,12 @@ export default class RoomsCreate extends Vue {
// eslint-disable-next-line
broadcast_pipeline: this.broadcastPipelineEnabled ? this.data.broadcast_pipeline : '',
envs,
// eslint-disable-next-line
browser_policy: this.browserPolicyEnabled && this.browserPolicyConfig ? {
type: this.browserPolicyConfig.type,
path: this.browserPolicyConfig.path,
content: this.browserPolicyContent
} : undefined,
})
this.Clear()
this.$emit('finished', true)
@@ -436,6 +561,7 @@ export default class RoomsCreate extends Vue {
// eslint-disable-next-line
neko_image: this.nekoImages[0],
}
this.browserPolicyContent = { ...this.$store.state.defaultBrowserPolicyContent }
this.envList = Object.entries({...this.data.envs}).map(([ key, val ]) => ({ key, val, }))
}
+103
View File
@@ -2,9 +2,30 @@ import {
RoomsConfig,
RoomEntry,
RoomSettings,
BrowserPolicyContent,
BrowserPolicyTypeEnum,
BrowserPolicyExtension,
} from '@/api/index'
export interface BrowserPolicyConfig {
type: BrowserPolicyTypeEnum;
path: string;
profile: string;
images: string[];
}
export interface BrowserPolicyExtensions {
type: BrowserPolicyTypeEnum;
path: string;
profile: string;
images: string[];
}
export const state = {
//
// rooms
//
roomsConfig: {} as RoomsConfig,
rooms: [] as RoomEntry[],
defaultRoomSettings: {
@@ -95,6 +116,88 @@ export const state = {
"640x400@60",
"640x360@60"
] as string[],
//
// browser policy
//
browserPolicyConfig: [
{
type: BrowserPolicyTypeEnum.firefox,
path: '/usr/lib/firefox/distribution/policies.json',
profile: '/home/neko/.mozilla/firefox/profile.default',
images: [ 'm1k1o/neko:latest', 'm1k1o/neko:firefox' ],
},
{
type: BrowserPolicyTypeEnum.firefox,
path: '/usr/lib/firefox-esr/distribution/policies.json',
profile: '/home/neko/.mozilla/firefox/profile.default',
images: [ 'm1k1o/neko:arm-firefox' ],
},
{
type: BrowserPolicyTypeEnum.chromium,
path: '/etc/chromium/policies/managed/policies.json',
profile: '/home/neko/.config/chromium',
images: [ 'm1k1o/neko:chromium', 'm1k1o/neko:arm-chromium', 'm1k1o/neko:ungoogled-chromium' ],
},
{
type: BrowserPolicyTypeEnum.chromium,
path: '/etc/opt/chrome/policies/managed/policies.json',
profile: '/home/neko/.config/google-chrome',
images: [ 'm1k1o/neko:google-chrome' ],
},
{
type: BrowserPolicyTypeEnum.chromium,
path: '/etc/brave/policies/managed/policies.json',
profile: '/home/neko/.config/brave',
images: [ 'm1k1o/neko:brave' ],
},
] as BrowserPolicyConfig[],
browserPolicyExtensions: [
{
text: 'uBlock Origin',
value: {
[BrowserPolicyTypeEnum.firefox]: {
id: 'uBlock0@raymondhill.net',
url: 'https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi',
} as BrowserPolicyExtension,
[BrowserPolicyTypeEnum.chromium]: {
id: 'cjpalhdlnbpafiamejdnhcphjbkeiagm',
} as BrowserPolicyExtension,
},
},
{
text: 'NordVPN',
value: {
[BrowserPolicyTypeEnum.firefox]: {
id: 'nordvpnproxy@nordvpn.com',
url: 'https://addons.mozilla.org/firefox/downloads/latest/nordvpn-proxy-extension/latest.xpi',
} as BrowserPolicyExtension,
[BrowserPolicyTypeEnum.chromium]: {
id: 'fjoaledfpmneenckfbpdfhkmimnjocfa',
} as BrowserPolicyExtension,
},
},
{
text: 'SponsorBlock for YouTube',
value: {
[BrowserPolicyTypeEnum.firefox]: {
id: 'sponsorBlocker@ajay.app',
url: 'https://addons.mozilla.org/firefox/downloads/latest/sponsorblock/latest.xpi',
} as BrowserPolicyExtension,
[BrowserPolicyTypeEnum.chromium]: {
id: 'mnjggcdmjocbbbhaepdhchncahnbgone',
} as BrowserPolicyExtension,
},
},
],
defaultBrowserPolicyContent: {
extensions: [],
// eslint-disable-next-line
developer_tools: false,
// eslint-disable-next-line
persistent_data: false,
} as BrowserPolicyContent,
}
export type State = typeof state
+84
View File
@@ -0,0 +1,84 @@
package chromium
// https://chromeenterprise.google/policies/
import (
_ "embed"
"encoding/json"
"fmt"
"m1k1o/neko_rooms/internal/types"
)
//go:embed policies.json
var policiesJson string
func Generate(policies types.BrowserPolicyContent) (string, error) {
policiesTmpl := map[string]interface{}{}
if err := json.Unmarshal([]byte(policiesJson), &policiesTmpl); err != nil {
return "", err
}
//
// Extensions
//
ExtensionInstallForcelist := []interface{}{}
for _, e := range policies.Extensions {
URL := e.URL
if URL == "" {
URL = "https://clients2.google.com/service/update2/crx"
}
ExtensionInstallForcelist = append(
ExtensionInstallForcelist,
fmt.Sprintf("%s;%s", e.ID, URL),
)
}
ExtensionInstallWhitelist := []interface{}{}
for _, e := range policies.Extensions {
ExtensionInstallWhitelist = append(
ExtensionInstallWhitelist,
e.ID,
)
}
policiesTmpl["ExtensionInstallForcelist"] = ExtensionInstallForcelist
policiesTmpl["ExtensionInstallWhitelist"] = ExtensionInstallWhitelist
policiesTmpl["ExtensionInstallBlacklist"] = []interface{}{"*"}
//
// Developer Tools
//
if policies.DeveloperTools {
// Allow usage of the Developer Tools
policiesTmpl["DeveloperToolsAvailability"] = 1
} else {
// Disallow usage of the Developer Tools
policiesTmpl["DeveloperToolsAvailability"] = 2
}
//
// Persistent Data
//
if policies.PersistentData {
// Allow all sites to set local data
policiesTmpl["DefaultCookiesSetting"] = 1
// Restore the last session
policiesTmpl["RestoreOnStartup"] = 1
} else {
// Keep cookies for the duration of the session
policiesTmpl["DefaultCookiesSetting"] = 4
// Open New Tab Page
policiesTmpl["RestoreOnStartup"] = 5
}
data, err := json.MarshalIndent(policiesTmpl, "", " ")
if err != nil {
return "", err
}
return string(data), nil
}
+54
View File
@@ -0,0 +1,54 @@
package chromium
// https://chromeenterprise.google/policies/
import (
"encoding/json"
"m1k1o/neko_rooms/internal/types"
"strings"
)
func Parse(policiesJson string) (*types.BrowserPolicyContent, error) {
policies := types.BrowserPolicyContent{}
policiesTmpl := map[string]interface{}{}
if err := json.Unmarshal([]byte(policiesJson), &policiesTmpl); err != nil {
return nil, err
}
//
// Extensions
//
if extensions, ok := policiesTmpl["ExtensionInstallForcelist"]; ok {
policies.Extensions = []types.BrowserPolicyExtension{}
for _, val := range extensions.([]interface{}) {
s := strings.Split(val.(string), ";")
policies.Extensions = append(
policies.Extensions,
types.BrowserPolicyExtension{
ID: s[0],
URL: s[1],
},
)
}
}
//
// Developer Tools
//
if val, ok := policiesTmpl["DeveloperToolsAvailability"]; ok {
policies.DeveloperTools = val.(float64) == 1
}
//
// Persistent Data
//
if val, ok := policiesTmpl["DefaultCookiesSetting"]; ok {
policies.PersistentData = val.(float64) == 1
}
return &policies, nil
}
+38
View File
@@ -0,0 +1,38 @@
{
"HomepageLocation": "",
"AutoFillEnabled": false,
"AutofillAddressEnabled": false,
"AutofillCreditCardEnabled": false,
"BrowserSignin": 0,
"DefaultNotificationsSetting": 2,
"DeveloperToolsAvailability": 2,
"EditBookmarksEnabled": false,
"FullscreenAllowed": true,
"IncognitoModeAvailability": 1,
"SyncDisabled": true,
"AutoplayAllowed": true,
"BrowserAddPersonEnabled": false,
"BrowserGuestModeEnabled": false,
"DefaultPopupsSetting": 2,
"DownloadRestrictions": 3,
"VideoCaptureAllowed": true,
"AllowFileSelectionDialogs": false,
"PromptForDownloadLocation": false,
"BookmarkBarEnabled": false,
"PasswordManagerEnabled": false,
"URLBlacklist": [
"file://*",
"chrome://policy"
],
"ExtensionInstallForcelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
"fjoaledfpmneenckfbpdfhkmimnjocfa;https://clients2.google.com/service/update2/crx"
],
"ExtensionInstallWhitelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
"fjoaledfpmneenckfbpdfhkmimnjocfa"
],
"ExtensionInstallBlacklist": [
"*"
]
}
+32
View File
@@ -0,0 +1,32 @@
package policies
import (
"errors"
"m1k1o/neko_rooms/internal/policies/chromium"
"m1k1o/neko_rooms/internal/policies/firefox"
"m1k1o/neko_rooms/internal/types"
)
func Generate(policies types.BrowserPolicyContent, policyType types.BrowserPolicyType) (string, error) {
if policyType == types.ChromiumBrowserPolicy {
return chromium.Generate(policies)
}
if policyType == types.FirefoxBrowserPolicy {
return firefox.Generate(policies)
}
return "", errors.New("unknown policy type")
}
func Parse(policiesJson string, policyType types.BrowserPolicyType) (*types.BrowserPolicyContent, error) {
if policyType == types.ChromiumBrowserPolicy {
return chromium.Parse(policiesJson)
}
if policyType == types.FirefoxBrowserPolicy {
return firefox.Parse(policiesJson)
}
return nil, errors.New("unknown policy type")
}
+72
View File
@@ -0,0 +1,72 @@
package firefox
// https://github.com/mozilla/policy-templates/blob/master/README.md#homepage
import (
_ "embed"
"encoding/json"
"m1k1o/neko_rooms/internal/types"
)
//go:embed policies.json
var policiesJson string
func Generate(policies types.BrowserPolicyContent) (string, error) {
policiesTmpl := struct {
Policies map[string]interface{} `json:"policies"`
}{}
if err := json.Unmarshal([]byte(policiesJson), &policiesTmpl); err != nil {
return "", err
}
//
// Extensions
//
ExtensionSettings := map[string]interface{}{}
ExtensionSettings["*"] = map[string]interface{}{
"installation_mode": "blocked",
}
for _, e := range policies.Extensions {
ExtensionSettings[e.ID] = map[string]interface{}{
"install_url": e.URL,
"installation_mode": "force_installed",
}
}
policiesTmpl.Policies["ExtensionSettings"] = ExtensionSettings
//
// Developer Tools
//
policiesTmpl.Policies["DisableDeveloperTools"] = !policies.DeveloperTools
//
// Persistent Data
//
Preferences := policiesTmpl.Policies["Preferences"].(map[string]interface{})
Preferences["browser.urlbar.suggest.history"] = policies.PersistentData
Preferences["places.history.enabled"] = policies.PersistentData
policiesTmpl.Policies["Preferences"] = Preferences
policiesTmpl.Policies["SanitizeOnShutdown"] = !policies.PersistentData
if policies.PersistentData {
policiesTmpl.Policies["Homepage"] = map[string]interface{}{
"StartPage": "previous-session",
}
} else {
policiesTmpl.Policies["Homepage"] = map[string]interface{}{
"StartPage": "homepage",
}
}
data, err := json.MarshalIndent(policiesTmpl, "", " ")
if err != nil {
return "", err
}
return string(data), nil
}
+67
View File
@@ -0,0 +1,67 @@
package firefox
// https://github.com/mozilla/policy-templates/blob/master/README.md#homepage
import (
_ "embed"
"encoding/json"
"m1k1o/neko_rooms/internal/types"
)
func Parse(policiesJson string) (*types.BrowserPolicyContent, error) {
policies := types.BrowserPolicyContent{}
policiesTmpl := struct {
Policies map[string]interface{} `json:"policies"`
}{}
if err := json.Unmarshal([]byte(policiesJson), &policiesTmpl); err != nil {
return nil, err
}
// empty file
if policiesTmpl.Policies == nil {
return &policies, nil
}
//
// Extensions
//
if extensions, ok := policiesTmpl.Policies["ExtensionSettings"]; ok {
policies.Extensions = []types.BrowserPolicyExtension{}
for id, val := range extensions.(map[string]interface{}) {
if id == "*" {
continue
}
data := val.(map[string]interface{})
url, _ := data["install_url"].(string)
policies.Extensions = append(
policies.Extensions,
types.BrowserPolicyExtension{
ID: id,
URL: url,
},
)
}
}
//
// Developer Tools
//
if val, ok := policiesTmpl.Policies["DisableDeveloperTools"]; ok {
policies.DeveloperTools = !val.(bool)
}
//
// Persistent Data
//
if val, ok := policiesTmpl.Policies["SanitizeOnShutdown"]; ok {
policies.PersistentData = !val.(bool)
}
return &policies, nil
}
+120
View File
@@ -0,0 +1,120 @@
{
"policies": {
"BlockAboutAddons": false,
"BlockAboutConfig": true,
"BlockAboutProfiles": true,
"BlockAboutSupport": true,
"Bookmarks": [
{
"Title": "IPLeak",
"URL": "https://ipleak.net/",
"Favicon": "https://ipleak.net/favicon.ico",
"Folder": "Pages",
"Placement": "toolbar"
},
{
"Title": "YouTube",
"URL": "https://www.youtube.com/",
"Favicon": "https://www.youtube.com/favicon.ico",
"Folder": "Pages",
"Placement": "toolbar"
},
{
"Title": "Google",
"URL": "https://www.google.com/",
"Favicon": "https://www.google.com/favicon.ico",
"Folder": "Pages",
"Placement": "toolbar"
}
],
"CaptivePortal": false,
"DisableAppUpdate": true,
"DisableBuiltinPDFViewer": true,
"DisableDeveloperTools": false,
"DisableFeedbackCommands": true,
"DisableFirefoxAccounts": true,
"DisableFirefoxScreenshots": true,
"DisableFirefoxStudies": true,
"DisableForgetButton": true,
"DisableMasterPasswordCreation": true,
"DisablePocket": true,
"DisablePrivateBrowsing": true,
"DisableProfileImport": true,
"DisableProfileRefresh": true,
"DisableSafeMode": true,
"DisableSetDesktopBackground": true,
"DisableSystemAddonUpdate": true,
"DisableTelemetry": true,
"DisplayBookmarksToolbar": false,
"DontCheckDefaultBrowser": true,
"EnableTrackingProtection": {
"Cryptomining": true,
"Fingerprinting": true,
"Value": true
},
"ExtensionSettings": {
"*": {
"installation_mode": "blocked"
},
"nordvpnproxy@nordvpn.com": {
"install_url": "https://addons.mozilla.org/firefox/downloads/latest/nordvpn-proxy-extension/latest.xpi",
"installation_mode": "force_installed"
},
"uBlock0@raymondhill.net": {
"install_url": "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi",
"installation_mode": "force_installed"
}
},
"ExtensionUpdate": false,
"FirefoxHome": {
"Highlights": false,
"Pocket": false,
"Search": true,
"Snippets": false,
"TopSites": true
},
"FlashPlugin": {},
"HardwareAcceleration": false,
"Homepage": {
"Additional": [],
"StartPage": "home"
},
"NewTabPage": true,
"NoDefaultBookmarks": true,
"OfferToSaveLogins": false,
"OfferToSaveLoginsDefault": false,
"OverrideFirstRunPage": "",
"OverridePostUpdatePage": "",
"PasswordManagerEnabled": false,
"Permissions": {
"Camera": {
"BlockNewRequests": true
},
"Location": {
"BlockNewRequests": true
},
"Microphone": {
"BlockNewRequests": true
},
"Notifications": {
"BlockNewRequests": true
}
},
"Preferences": {
"browser.tabs.warnOnClose": false,
"browser.urlbar.suggest.bookmark": false,
"browser.urlbar.suggest.history": false,
"browser.urlbar.suggest.openpage": false,
"datareporting.policy.dataSubmissionPolicyBypassNotification": true,
"dom.disable_window_flip": true,
"dom.disable_window_move_resize": true,
"dom.event.contextmenu.enabled": false,
"extensions.getAddons.showPane": false,
"places.history.enabled": false,
"privacy.file_unique_origin": true,
"ui.key.menuAccessKeyFocuses": false
},
"PromptForDownloadLocation": false,
"SanitizeOnShutdown": true
}
}
+37 -1
View File
@@ -3,6 +3,8 @@ package room
import (
"fmt"
"strconv"
"m1k1o/neko_rooms/internal/types"
)
type RoomLabels struct {
@@ -10,6 +12,13 @@ type RoomLabels struct {
URL string
Epr EprPorts
NekoImage string
BrowserPolicy *BrowserPolicyLabels
}
type BrowserPolicyLabels struct {
Type types.BrowserPolicyType
Path string
}
func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLabels, error) {
@@ -48,6 +57,24 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
return nil, err
}
var browserPolicy *BrowserPolicyLabels
if val, ok := labels["m1k1o.neko_rooms.browser_policy"]; ok && val == "true" {
policyType, ok := labels["m1k1o.neko_rooms.browser_policy.type"]
if !ok {
return nil, fmt.Errorf("damaged container labels: browser_policy.type not found")
}
policyPath, ok := labels["m1k1o.neko_rooms.browser_policy.path"]
if !ok {
return nil, fmt.Errorf("damaged container labels: browser_policy.path not found")
}
browserPolicy = &BrowserPolicyLabels{
Type: types.BrowserPolicyType(policyType),
Path: policyPath,
}
}
return &RoomLabels{
Name: name,
URL: url,
@@ -56,11 +83,12 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
Min: uint16(eprMin),
Max: uint16(eprMax),
},
BrowserPolicy: browserPolicy,
}, nil
}
func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]string {
return map[string]string{
labelsMap := map[string]string{
"m1k1o.neko_rooms.name": labels.Name,
"m1k1o.neko_rooms.url": labels.URL,
"m1k1o.neko_rooms.instance": manager.config.InstanceName,
@@ -68,4 +96,12 @@ func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]str
"m1k1o.neko_rooms.epr.max": fmt.Sprintf("%d", labels.Epr.Max),
"m1k1o.neko_rooms.neko_image": labels.NekoImage,
}
if labels.BrowserPolicy != nil {
labelsMap["m1k1o.neko_rooms.browser_policy"] = "true"
labelsMap["m1k1o.neko_rooms.browser_policy.type"] = string(labels.BrowserPolicy.Type)
labelsMap["m1k1o.neko_rooms.browser_policy.path"] = labels.BrowserPolicy.Path
}
return labelsMap
}
+88
View File
@@ -21,6 +21,7 @@ import (
"github.com/rs/zerolog/log"
"m1k1o/neko_rooms/internal/config"
"m1k1o/neko_rooms/internal/policies"
"m1k1o/neko_rooms/internal/types"
"m1k1o/neko_rooms/internal/utils"
)
@@ -60,6 +61,7 @@ func (manager *RoomManagerCtx) Config() types.RoomsConfig {
return types.RoomsConfig{
Connections: manager.config.EprMax - manager.config.EprMin + 1,
NekoImages: manager.config.NekoImages,
StorageEnabled: manager.config.StorageEnabled,
}
}
@@ -215,11 +217,21 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
instanceUrl = instanceUrl + "/"
}
var browserPolicyLabels *BrowserPolicyLabels
if settings.BrowserPolicy != nil {
browserPolicyLabels = &BrowserPolicyLabels{
Type: settings.BrowserPolicy.Type,
Path: settings.BrowserPolicy.Path,
}
}
labels := manager.serializeLabels(RoomLabels{
Name: roomName,
URL: instanceUrl + instanceSuffix,
Epr: epr,
NekoImage: settings.NekoImage,
BrowserPolicy: browserPolicyLabels,
})
for k, v := range traefikLabels {
@@ -241,12 +253,57 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
env = append(env, fmt.Sprintf("NEKO_NAT1TO1=%s", strings.Join(manager.config.NAT1To1IPs, ",")))
}
//
// Set browser policies
//
if settings.BrowserPolicy != nil {
if !manager.config.StorageEnabled {
return "", fmt.Errorf("policies cannot be specified, because storage is disabled or unavailable")
}
policyJson, err := policies.Generate(settings.BrowserPolicy.Content, settings.BrowserPolicy.Type)
if err != nil {
return "", err
}
// create policy path (+ also get host path)
policyPath := fmt.Sprintf("/%s-%s-policy.json", roomName, settings.BrowserPolicy.Type)
templateInternalPath := path.Join(manager.config.StorageInternal, templateStoragePath)
policyInternalPath := path.Join(templateInternalPath, policyPath)
// create dir if does not exist
if _, err := os.Stat(templateInternalPath); os.IsNotExist(err) {
if err := os.MkdirAll(templateInternalPath, os.ModePerm); err != nil {
return "", err
}
}
// write policy to file
if err := os.WriteFile(policyInternalPath, []byte(policyJson), 0644); err != nil {
return "", err
}
// mount policy file
settings.Mounts = append(settings.Mounts, types.RoomMount{
Type: types.MountTemplate,
HostPath: policyPath,
ContainerPath: settings.BrowserPolicy.Path,
})
}
//
// Set container mounts
//
paths := map[string]bool{}
mounts := []dockerMount.Mount{}
for _, mount := range settings.Mounts {
// ignore duplicates
if _, ok := paths[mount.ContainerPath]; ok {
continue
}
readOnly := false
hostPath := filepath.Clean(mount.HostPath)
@@ -309,6 +366,8 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
},
},
)
paths[mount.ContainerPath] = true
}
//
@@ -441,11 +500,40 @@ func (manager *RoomManagerCtx) GetSettings(id string) (*types.RoomSettings, erro
})
}
var browserPolicy *types.BrowserPolicy
if labels.BrowserPolicy != nil {
browserPolicy = &types.BrowserPolicy{
Type: labels.BrowserPolicy.Type,
Path: labels.BrowserPolicy.Path,
}
var policyMount *types.RoomMount
for _, mount := range mounts {
if mount.ContainerPath == labels.BrowserPolicy.Path {
policyMount = &mount
break
}
}
// TODO: Refactor.
if policyMount != nil && policyMount.Type == types.MountTemplate {
templateInternalPath := path.Join(manager.config.StorageInternal, templateStoragePath, policyMount.HostPath)
if _, err := os.Stat(templateInternalPath); !os.IsNotExist(err) {
if data, err := os.ReadFile(templateInternalPath); err == nil {
if content, err := policies.Parse(string(data), labels.BrowserPolicy.Type); err == nil {
browserPolicy.Content = *content
}
}
}
}
}
settings := types.RoomSettings{
Name: labels.Name,
NekoImage: labels.NekoImage,
MaxConnections: labels.Epr.Max - labels.Epr.Min + 1,
Mounts: mounts,
BrowserPolicy: browserPolicy,
}
err = settings.FromEnv(container.Config.Env)
+25
View File
@@ -0,0 +1,25 @@
package types
type BrowserPolicyType string
const (
ChromiumBrowserPolicy BrowserPolicyType = "chromium"
FirefoxBrowserPolicy BrowserPolicyType = "firefox"
)
type BrowserPolicy struct {
Type BrowserPolicyType `json:"type"`
Path string `json:"path"`
Content BrowserPolicyContent `json:"content"`
}
type BrowserPolicyContent struct {
Extensions []BrowserPolicyExtension `json:"extensions"`
DeveloperTools bool `json:"developer_tools"`
PersistentData bool `json:"persistent_data"`
}
type BrowserPolicyExtension struct {
ID string `json:"id"`
URL string `json:"url"`
}
+3
View File
@@ -26,6 +26,7 @@ var blacklistedEnvs = []string{
type RoomsConfig struct {
Connections uint16 `json:"connections"`
NekoImages []string `json:"neko_images"`
StorageEnabled bool `json:"storage_enabled"`
}
type RoomEntry struct {
@@ -75,6 +76,8 @@ type RoomSettings struct {
Envs map[string]string `json:"envs"`
Mounts []RoomMount `json:"mounts"`
BrowserPolicy *BrowserPolicy `json:"browser_policy,omitempty"`
}
func (settings *RoomSettings) ToEnv() []string {