1
0
mirror of https://github.com/amir20/dozzle.git synced 2026-01-02 19:17:37 +01:00

Implements Content-Security-Policy (#442)

This commit is contained in:
Amir Raminfar
2020-05-08 19:45:08 -07:00
committed by GitHub
parent b04e950358
commit 99f7e0d980
14 changed files with 73 additions and 49 deletions

View File

@@ -1,6 +1,7 @@
/* snapshot: Test_createRoutes_foobar */
HTTP/1.1 200 OK
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
Content-Type: text/plain; charset=utf-8
foo page
@@ -8,6 +9,7 @@ foo page
/* snapshot: Test_createRoutes_index */
HTTP/1.1 200 OK
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
Content-Type: text/plain; charset=utf-8
index page
@@ -15,6 +17,7 @@ index page
/* snapshot: Test_createRoutes_redirect */
HTTP/1.1 301 Moved Permanently
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
Content-Type: text/html; charset=utf-8
Location: /foobar/
@@ -23,6 +26,7 @@ Location: /foobar/
/* snapshot: Test_createRoutes_version */
HTTP/1.1 200 OK
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
Content-Type: text/plain; charset=utf-8
dev

View File

@@ -3,6 +3,8 @@ import { shallowMount, RouterLinkStub, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import App from "./App";
jest.mock("./store/config.js", () => ({ base: "" }));
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -12,7 +14,6 @@ describe("<App />", () => {
let store;
beforeEach(() => {
global.BASE_PATH = "";
global.EventSource = EventSource;
const state = {
containers: [

View File

@@ -13,9 +13,10 @@ jest.mock("lodash.debounce", () =>
})
);
jest.mock("../store/config.js", () => ({ base: "" }));
describe("<LogEventSource />", () => {
beforeEach(() => {
global.BASE_PATH = "";
global.EventSource = EventSource;
MockDate.set("6/12/2019", 0);
window.scrollTo = jest.fn();
@@ -57,17 +58,7 @@ describe("<LogEventSource />", () => {
test("renders correctly", async () => {
const wrapper = createLogEventSource();
expect(wrapper.element).toMatchInlineSnapshot(`
<div>
<div
class="control"
/>
<ul
class="events medium"
/>
</div>
`);
expect(wrapper.element).toMatchSnapshot();
});
test("should connect to EventSource", async () => {

View File

@@ -8,6 +8,7 @@
<script>
import debounce from "lodash.debounce";
import InfiniteLoader from "./InfiniteLoader";
import config from "../store/config";
export default {
props: ["id"],
@@ -33,7 +34,7 @@ export default {
this.buffer = [];
this.es = null;
}
this.es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${this.id}`);
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}`);
const flushBuffer = debounce(
() => {
this.messages.push(...this.buffer);

View File

@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LogEventSource /> renders correctly 1`] = `
<div>
<div
class="control"
/>
<ul
class="events medium"
/>
</div>
`;

View File

@@ -4,17 +4,18 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Dozzle</title>
<link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono|Gafata" rel="stylesheet" />
<script>
window["BASE_PATH"] = "{{ .Base }}";
window["VERSION"] = "{{ .Version }}";
<link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono|Gafata" rel="stylesheet" />
<script type="application/json" id="config__json">
{
"base": "{{ .Base }}",
"version": "{{ .Version }}"
}
</script>
</head>
<body>
<svg
aria-hidden="true"
style="position: absolute; width: 0; height: 0; overflow: hidden;"
class="is-hidden"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"

View File

@@ -4,6 +4,7 @@ import Meta from "vue-meta";
import Dropdown from "buefy/dist/esm/dropdown";
import Switch from "buefy/dist/esm/switch";
import store from "./store";
import config from "./store/config";
import App from "./App.vue";
import Container from "./pages/Container.vue";
import Settings from "./pages/Settings.vue";
@@ -35,7 +36,7 @@ const routes = [
const router = new VueRouter({
mode: "history",
base: BASE_PATH + "/",
base: config.base + "/",
routes,
});

View File

@@ -72,6 +72,7 @@ import gt from "semver/functions/gt";
import valid from "semver/functions/valid";
import { mapActions, mapState } from "vuex";
import Icon from "../components/Icon";
import config from "../store/config";
export default {
props: [],
@@ -81,7 +82,7 @@ export default {
},
data() {
return {
currentVersion: VERSION,
currentVersion: config.version,
nextRelease: null,
hasUpdate: false,
};
@@ -117,7 +118,7 @@ export default {
},
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.title {
color: #eee;
}

2
assets/store/config.js Normal file
View File

@@ -0,0 +1,2 @@
const config = JSON.parse(document.querySelector("script#config__json").textContent);
export default config;

View File

@@ -2,6 +2,7 @@ import Vue from "vue";
import Vuex from "vuex";
import storage from "store/dist/store.modern";
import { DEFAULT_SETTINGS, DOZZLE_SETTINGS_KEY } from "./settings";
import config from "./config";
Vue.use(Vuex);
@@ -50,7 +51,7 @@ const actions = {
commit("SET_SEARCH", filter);
},
async FETCH_CONTAINERS({ commit }) {
const containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
const containers = await (await fetch(`${config.base}/api/containers.json`)).json();
commit("SET_CONTAINERS", containers);
},
UPDATE_SETTING({ commit }, setting) {
@@ -72,7 +73,7 @@ const getters = {
},
};
const es = new EventSource(`${BASE_PATH}/api/events/stream`);
const es = new EventSource(`${config.base}/api/events/stream`);
es.addEventListener("containers-changed", (e) => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
mql.addListener((e) => store.commit("SET_MOBILE_WIDTH", e.matches));

18
main.go
View File

@@ -11,7 +11,6 @@ import (
"github.com/amir20/dozzle/docker"
"github.com/gobuffalo/packr"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"github.com/spf13/viper"
@@ -75,23 +74,6 @@ func init() {
})
}
func createRoutes(base string, h *handler) *mux.Router {
r := mux.NewRouter()
if base != "/" {
r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
}))
}
s := r.PathPrefix(base).Subrouter()
s.HandleFunc("/api/containers.json", h.listContainers)
s.HandleFunc("/api/logs/stream", h.streamLogs)
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
s.HandleFunc("/api/events/stream", h.streamEvents)
s.HandleFunc("/version", h.version)
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
return r
}
func main() {
log.Infof("Dozzle version %s", version)
dockerClient := docker.NewClientWithFilters(filters)

View File

@@ -3,11 +3,11 @@
"version": "1.23.2",
"description": "Realtime log viewer for docker containers. ",
"scripts": {
"prestart": "npm run clean",
"start": "concurrently 'npm run watch-server' 'npm run watch-assets'",
"prestart": "yarn clean",
"start": "concurrently 'yarn watch-server' 'yarn watch-assets'",
"watch-assets": "webpack --mode=development --watch",
"watch-server": "reflex -c .reflex",
"prebuild": "npm run clean",
"prebuild": "yarn clean",
"build": "yarn webpack --mode=production",
"clean": "rm -rf static/ a_main-packr.go",
"release": "release-it",

View File

@@ -8,9 +8,35 @@ import (
"runtime"
"time"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)
func createRoutes(base string, h *handler) *mux.Router {
r := mux.NewRouter()
r.Use(setCSPHeaders)
if base != "/" {
r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
}))
}
s := r.PathPrefix(base).Subrouter()
s.HandleFunc("/api/containers.json", h.listContainers)
s.HandleFunc("/api/logs/stream", h.streamLogs)
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
s.HandleFunc("/api/events/stream", h.streamEvents)
s.HandleFunc("/version", h.version)
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
return r
}
func setCSPHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'")
next.ServeHTTP(w, r)
})
}
func (h *handler) index(w http.ResponseWriter, req *http.Request) {
fileServer := http.FileServer(h.box)
if h.box.Has(req.URL.Path) && req.URL.Path != "" && req.URL.Path != "/" {
@@ -94,7 +120,6 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
log.Debugf("Starting to stream logs for %s", id)
Loop:
for {

View File

@@ -4,12 +4,13 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackPwaManifest = require("webpack-pwa-manifest");
module.exports = {
module.exports = (env, argv) => ({
stats: { children: false, entrypoints: false, modules: false },
performance: {
maxAssetSize: 350000,
maxEntrypointSize: 570000,
},
devtool: argv.mode === "development" ? "inline-cheap-source-map" : false,
entry: ["./assets/main.js", "./assets/styles.scss"],
output: {
path: path.resolve(__dirname, "./static"),
@@ -74,4 +75,4 @@ module.exports = {
},
extensions: ["*", ".js", ".vue", ".json"],
},
};
});