mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Add ability to prefix Fleet URLs (#2112)
- Add the server_url_prefix flag for configuring this functionality - Add prefix handling to the server routes - Refactor JS to use appropriate paths from modules - Use JS template to get URL prefix into JS environment - Update webpack config to support prefixing Thanks to securityonion.net for sponsoring the development of this feature. Closes #1661
This commit is contained in:
parent
59efb495ca
commit
adf87140a7
52 changed files with 312 additions and 199 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -10,6 +10,10 @@ node_modules
|
|||
assets/bundle*.*
|
||||
assets/*@*.svg
|
||||
assets/*@*.png
|
||||
assets/*@*.eot
|
||||
assets/*@*.woff
|
||||
assets/*@*.woff2
|
||||
assets/*@*.ttf
|
||||
frontend/templates/react.tmpl
|
||||
bindata.go
|
||||
*.cover
|
||||
|
|
@ -23,4 +27,4 @@ tmp/
|
|||
.DS_Store
|
||||
|
||||
# test mysql server data
|
||||
mysqldata/
|
||||
mysqldata/
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
|
@ -27,12 +29,15 @@ import (
|
|||
"github.com/kolide/fleet/server/service"
|
||||
"github.com/kolide/fleet/server/sso"
|
||||
"github.com/kolide/kit/version"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var allowedURLPrefixRegexp = regexp.MustCompile("^(?:/[a-zA-Z0-9_.~-]+)+$")
|
||||
|
||||
type initializer interface {
|
||||
// Initialize is used to populate a datastore with
|
||||
// preloaded data
|
||||
|
|
@ -68,6 +73,7 @@ the way that the Fleet server works.
|
|||
logger = kitlog.With(logger, "ts", kitlog.DefaultTimestampUTC)
|
||||
}
|
||||
|
||||
// Check for deprecated config options.
|
||||
if config.Osquery.StatusLogFile != "" {
|
||||
level.Info(logger).Log(
|
||||
"DEPRECATED", "use filesystem.status_log_file.",
|
||||
|
|
@ -90,6 +96,21 @@ the way that the Fleet server works.
|
|||
config.Filesystem.EnableLogRotation = config.Osquery.EnableLogRotation
|
||||
}
|
||||
|
||||
if len(config.Server.URLPrefix) > 0 {
|
||||
// Massage provided prefix to match expected format
|
||||
config.Server.URLPrefix = strings.TrimSuffix(config.Server.URLPrefix, "/")
|
||||
if len(config.Server.URLPrefix) > 0 && !strings.HasPrefix(config.Server.URLPrefix, "/") {
|
||||
config.Server.URLPrefix = "/" + config.Server.URLPrefix
|
||||
}
|
||||
|
||||
if !allowedURLPrefixRegexp.MatchString(config.Server.URLPrefix) {
|
||||
initFatal(
|
||||
errors.Errorf("prefix must match regexp \"%s\"", allowedURLPrefixRegexp.String()),
|
||||
"setting server URL prefix",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var ds kolide.Datastore
|
||||
var err error
|
||||
mailService := mail.NewService()
|
||||
|
|
@ -190,8 +211,8 @@ the way that the Fleet server works.
|
|||
|
||||
var apiHandler, frontendHandler http.Handler
|
||||
{
|
||||
frontendHandler = prometheus.InstrumentHandler("get_frontend", service.ServeFrontend(httpLogger))
|
||||
apiHandler = service.MakeHandler(svc, config.Auth.JwtKey, httpLogger)
|
||||
frontendHandler = prometheus.InstrumentHandler("get_frontend", service.ServeFrontend(config.Server.URLPrefix, httpLogger))
|
||||
apiHandler = service.MakeHandler(svc, config, httpLogger)
|
||||
|
||||
setupRequired, err := service.RequireSetup(svc)
|
||||
if err != nil {
|
||||
|
|
@ -202,9 +223,9 @@ the way that the Fleet server works.
|
|||
// more efficient after the first startup.
|
||||
if setupRequired {
|
||||
apiHandler = service.WithSetup(svc, logger, apiHandler)
|
||||
frontendHandler = service.RedirectLoginToSetup(svc, logger, frontendHandler)
|
||||
frontendHandler = service.RedirectLoginToSetup(svc, logger, frontendHandler, config.Server.URLPrefix)
|
||||
} else {
|
||||
frontendHandler = service.RedirectSetupToLogin(svc, logger, frontendHandler)
|
||||
frontendHandler = service.RedirectSetupToLogin(svc, logger, frontendHandler, config.Server.URLPrefix)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -229,14 +250,13 @@ the way that the Fleet server works.
|
|||
// Instantiate a gRPC service to handle launcher requests.
|
||||
launcher := launcher.New(svc, logger, grpc.NewServer(), healthCheckers)
|
||||
|
||||
r := http.NewServeMux()
|
||||
|
||||
r.Handle("/healthz", prometheus.InstrumentHandler("healthz", health.Handler(httpLogger, healthCheckers)))
|
||||
r.Handle("/version", prometheus.InstrumentHandler("version", version.Handler()))
|
||||
r.Handle("/assets/", prometheus.InstrumentHandler("static_assets", service.ServeStaticAssets("/assets/")))
|
||||
r.Handle("/metrics", prometheus.InstrumentHandler("metrics", promhttp.Handler()))
|
||||
r.Handle("/api/", apiHandler)
|
||||
r.Handle("/", frontendHandler)
|
||||
rootMux := http.NewServeMux()
|
||||
rootMux.Handle("/healthz", prometheus.InstrumentHandler("healthz", health.Handler(httpLogger, healthCheckers)))
|
||||
rootMux.Handle("/version", prometheus.InstrumentHandler("version", version.Handler()))
|
||||
rootMux.Handle("/assets/", prometheus.InstrumentHandler("static_assets", service.ServeStaticAssets("/assets/")))
|
||||
rootMux.Handle("/metrics", prometheus.InstrumentHandler("metrics", promhttp.Handler()))
|
||||
rootMux.Handle("/api/", apiHandler)
|
||||
rootMux.Handle("/", frontendHandler)
|
||||
|
||||
if path, ok := os.LookupEnv("KOLIDE_TEST_PAGE_PATH"); ok {
|
||||
// test that we can load this
|
||||
|
|
@ -244,7 +264,7 @@ the way that the Fleet server works.
|
|||
if err != nil {
|
||||
initFatal(err, "loading KOLIDE_TEST_PAGE_PATH")
|
||||
}
|
||||
r.HandleFunc("/test", func(rw http.ResponseWriter, req *http.Request) {
|
||||
rootMux.HandleFunc("/test", func(rw http.ResponseWriter, req *http.Request) {
|
||||
testPage, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
|
|
@ -262,13 +282,20 @@ the way that the Fleet server works.
|
|||
if err != nil {
|
||||
initFatal(err, "generating debug token")
|
||||
}
|
||||
r.Handle("/debug/", http.StripPrefix("/debug/", netbug.AuthHandler(debugToken)))
|
||||
rootMux.Handle("/debug/", http.StripPrefix("/debug/", netbug.AuthHandler(debugToken)))
|
||||
fmt.Printf("*** Debug mode enabled ***\nAccess the debug endpoints at /debug/?token=%s\n", url.QueryEscape(debugToken))
|
||||
}
|
||||
|
||||
if len(config.Server.URLPrefix) > 0 {
|
||||
|
||||
prefixMux := http.NewServeMux()
|
||||
prefixMux.Handle(config.Server.URLPrefix+"/", http.StripPrefix(config.Server.URLPrefix, rootMux))
|
||||
rootMux = prefixMux
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: config.Server.Address,
|
||||
Handler: launcher.Handler(r),
|
||||
Handler: launcher.Handler(rootMux),
|
||||
ReadTimeout: 25 * time.Second,
|
||||
WriteTimeout: 40 * time.Second,
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ func unauthenticatedClientFromCLI(c *cli.Context) (*service.Client, error) {
|
|||
return nil, errors.New("set the Fleet API address with: fleetctl config set --address https://localhost:8080")
|
||||
}
|
||||
|
||||
fleet, err := service.NewClient(cc.Address, cc.TLSSkipVerify, cc.RootCA)
|
||||
fleet, err := service.NewClient(cc.Address, cc.TLSSkipVerify, cc.RootCA, cc.URLPrefix)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating Fleet API client handler")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ type Context struct {
|
|||
Token string `json:"token"`
|
||||
TLSSkipVerify bool `json:"tls-skip-verify"`
|
||||
RootCA string `json:"rootca"`
|
||||
URLPrefix string `json:"url-prefix"`
|
||||
}
|
||||
|
||||
func configFlag() cli.Flag {
|
||||
|
|
@ -122,6 +123,8 @@ func getConfigValue(c *cli.Context, key string) (interface{}, error) {
|
|||
} else {
|
||||
return false, nil
|
||||
}
|
||||
case "url-prefix":
|
||||
return currentContext.URLPrefix, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is an invalid key", key)
|
||||
}
|
||||
|
|
@ -166,6 +169,8 @@ func setConfigValue(c *cli.Context, key, value string) error {
|
|||
return errors.Wrapf(err, "error parsing %q as bool", value)
|
||||
}
|
||||
currentContext.TLSSkipVerify = boolValue
|
||||
case "url-prefix":
|
||||
currentContext.URLPrefix = value
|
||||
default:
|
||||
return fmt.Errorf("%q is an invalid option", key)
|
||||
}
|
||||
|
|
@ -186,6 +191,7 @@ func configSetCommand() cli.Command {
|
|||
flToken string
|
||||
flTLSSkipVerify bool
|
||||
flRootCA string
|
||||
flURLPrefix string
|
||||
)
|
||||
return cli.Command{
|
||||
Name: "set",
|
||||
|
|
@ -228,6 +234,13 @@ func configSetCommand() cli.Command {
|
|||
Destination: &flRootCA,
|
||||
Usage: "Specify RootCA chain used to communicate with fleet",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "url-prefix",
|
||||
EnvVar: "URL_PREFIX",
|
||||
Value: "",
|
||||
Destination: &flURLPrefix,
|
||||
Usage: "Specify URL Prefix to use with Fleet server (copy from server configuration)",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
set := false
|
||||
|
|
@ -272,6 +285,14 @@ func configSetCommand() cli.Command {
|
|||
fmt.Printf("[+] Set the rootca config key to %q in the %q context\n", flRootCA, c.String("context"))
|
||||
}
|
||||
|
||||
if flURLPrefix != "" {
|
||||
set = true
|
||||
if err := setConfigValue(c, "url-prefix", flURLPrefix); err != nil {
|
||||
return errors.Wrap(err, "error setting URL Prefix")
|
||||
}
|
||||
fmt.Printf("[+] Set the url-prefix config key to %q in the %q context\n", flURLPrefix, c.String("context"))
|
||||
}
|
||||
|
||||
if !set {
|
||||
return cli.ShowCommandHelp(c, "set")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -351,6 +351,21 @@ Configures the TLS settings for compatibility with various user agents. Options
|
|||
tls_compatibility: intermediate
|
||||
```
|
||||
|
||||
##### `server_url_prefix`
|
||||
|
||||
Sets a URL prefix to use when serving the Fleet API and frontend. Prefixes should be in the form `/apps/fleet` (no trailing slash).
|
||||
|
||||
Note that some other configurations may need to be changed when modifying the URL prefix. In particular, URLs that are provided to osquery via flagfile, the configuration served by Fleet, the URL prefix used by `fleetctl`, and the redirect URL set with an SSO Identity Provider.
|
||||
|
||||
- Default value: Empty (no prefix set)
|
||||
- Environment variable: `KOLIDE_SERVER_URL_PREFIX`
|
||||
- Config file format:
|
||||
|
||||
```
|
||||
server:
|
||||
url_prefix: /apps/fleet
|
||||
```
|
||||
|
||||
|
||||
#### Auth
|
||||
|
||||
|
|
@ -771,4 +786,3 @@ The identifier of the pubsub topic that osquery status logs will be published to
|
|||
pubsub:
|
||||
status_topic: osquery_status
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import logoVertical from '../../../assets/images/kolide-logo-vertical.svg';
|
||||
|
||||
const baseClass = 'auth-form-wrapper';
|
||||
|
||||
class AuthenticationFormWrapper extends Component {
|
||||
|
|
@ -13,7 +15,7 @@ class AuthenticationFormWrapper extends Component {
|
|||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<img alt="Kolide Fleet" src="/assets/images/kolide-logo-vertical.svg" className={`${baseClass}__logo`} />
|
||||
<img alt="Kolide Fleet" src={logoVertical} className={`${baseClass}__logo`} />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.avatar {
|
||||
background: $white url('/assets/images/avatar-default.png') center 100% no-repeat;
|
||||
background: $white url('../assets/images/avatar-default.png') center 100% no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: 50%;
|
||||
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||
const { location: { pathname, query } } = ownProps;
|
||||
const { token } = query;
|
||||
|
||||
const isForgotPassPage = pathname === '/login/forgot';
|
||||
const isResetPassPage = pathname === '/login/reset';
|
||||
const isForgotPassPage = pathname.endsWith('/login/forgot');
|
||||
const isResetPassPage = pathname.endsWith('/login/reset');
|
||||
|
||||
return {
|
||||
isForgotPassPage,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { push } from 'react-router-redux';
|
||||
import { join, omit, values } from 'lodash';
|
||||
|
||||
import PATHS from 'router/paths';
|
||||
import queryActions from 'redux/nodes/entities/queries/actions';
|
||||
import { renderFlash } from 'redux/nodes/notifications/actions';
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ export const fetchQuery = (dispatch, queryID) => {
|
|||
.catch((errors) => {
|
||||
const errorMessage = join(values(omit(errors, 'http_status')), ', ');
|
||||
|
||||
dispatch(push('/queries/new'));
|
||||
dispatch(push(PATHS.NEW_QUERY));
|
||||
dispatch(renderFlash('error', errorMessage));
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import classnames from 'classnames';
|
|||
|
||||
import Avatar from 'components/Avatar';
|
||||
import Icon from 'components/icons/Icon';
|
||||
import PATHS from 'router/paths';
|
||||
|
||||
class UserMenu extends Component {
|
||||
static propTypes = {
|
||||
|
|
@ -39,7 +40,7 @@ class UserMenu extends Component {
|
|||
|
||||
<nav className={`${toggleBaseClass}__nav`}>
|
||||
<ul className={`${toggleBaseClass}__nav-list`}>
|
||||
<li className={`${toggleBaseClass}__nav-item`}><a href="#settings" onClick={onNavItemClick('/settings')}><Icon name="user-settings" /><span>Account Settings</span></a></li>
|
||||
<li className={`${toggleBaseClass}__nav-item`}><a href="#settings" onClick={onNavItemClick(PATHS.USER_SETTINGS)}><Icon name="user-settings" /><span>Account Settings</span></a></li>
|
||||
<li className={`${toggleBaseClass}__nav-item`}><a href="#logout" onClick={onLogout}><Icon name="logout" /><span>Log Out</span></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,30 @@
|
|||
import PATHS from 'router/paths';
|
||||
import URL_PREFIX from 'router/url_prefix';
|
||||
|
||||
export default (admin) => {
|
||||
const adminNavItems = [
|
||||
{
|
||||
icon: 'admin',
|
||||
name: 'Admin',
|
||||
location: {
|
||||
regex: /^\/admin/,
|
||||
pathname: '/admin/users',
|
||||
regex: new RegExp(`^${URL_PREFIX}/admin/`),
|
||||
pathname: PATHS.ADMIN_USERS,
|
||||
},
|
||||
subItems: [
|
||||
{
|
||||
icon: 'admin',
|
||||
name: 'Manage Users',
|
||||
location: {
|
||||
regex: /\/admin\/users/,
|
||||
pathname: '/admin/users',
|
||||
regex: new RegExp(`^${PATHS.ADMIN_USERS}`),
|
||||
pathname: PATHS.ADMIN_USERS,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: 'user-settings',
|
||||
name: 'App Settings',
|
||||
location: {
|
||||
regex: /\/admin\/settings/,
|
||||
pathname: '/admin/settings',
|
||||
regex: new RegExp(`^${PATHS.ADMIN_SETTINGS}`),
|
||||
pathname: PATHS.ADMIN_SETTINGS,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -33,8 +36,8 @@ export default (admin) => {
|
|||
icon: 'hosts',
|
||||
name: 'Hosts',
|
||||
location: {
|
||||
regex: /^\/hosts/,
|
||||
pathname: '/hosts/manage',
|
||||
regex: new RegExp(`^${URL_PREFIX}/hosts/`),
|
||||
pathname: PATHS.MANAGE_HOSTS,
|
||||
},
|
||||
subItems: [],
|
||||
},
|
||||
|
|
@ -42,24 +45,24 @@ export default (admin) => {
|
|||
icon: 'query',
|
||||
name: 'Query',
|
||||
location: {
|
||||
regex: /^\/queries/,
|
||||
pathname: '/queries/manage',
|
||||
regex: new RegExp(`^${URL_PREFIX}/queries/`),
|
||||
pathname: PATHS.MANAGE_QUERIES,
|
||||
},
|
||||
subItems: [
|
||||
{
|
||||
icon: 'query',
|
||||
name: 'Manage Queries',
|
||||
location: {
|
||||
regex: /\/queries\/manage/,
|
||||
pathname: '/queries/manage',
|
||||
regex: new RegExp(`^${PATHS.MANAGE_QUERIES}`),
|
||||
pathname: PATHS.MANAGE_QUERIES,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: 'pencil',
|
||||
name: 'New Query',
|
||||
location: {
|
||||
regex: /\/queries\/new/,
|
||||
pathname: '/queries/new',
|
||||
regex: new RegExp(`^${PATHS.NEW_QUERY}`),
|
||||
pathname: PATHS.NEW_QUERY,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -68,24 +71,24 @@ export default (admin) => {
|
|||
icon: 'packs',
|
||||
name: 'Packs',
|
||||
location: {
|
||||
regex: /^\/packs/,
|
||||
pathname: '/packs/manage',
|
||||
regex: new RegExp(`^${URL_PREFIX}/packs/`),
|
||||
pathname: PATHS.MANAGE_PACKS,
|
||||
},
|
||||
subItems: [
|
||||
{
|
||||
icon: 'packs',
|
||||
name: 'Manage Packs',
|
||||
location: {
|
||||
regex: /\/packs\/manage/,
|
||||
pathname: '/packs/manage',
|
||||
regex: new RegExp(`^${PATHS.MANAGE_PACKS}`),
|
||||
pathname: PATHS.MANAGE_PACKS,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: 'pencil',
|
||||
name: 'New Pack',
|
||||
location: {
|
||||
regex: /\/packs\/new/,
|
||||
pathname: '/packs/new',
|
||||
regex: new RegExp(`^${PATHS.NEW_PACK}`),
|
||||
pathname: PATHS.NEW_PACK,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import ReactDOM from 'react-dom';
|
||||
|
||||
import './public-path';
|
||||
import routes from './router';
|
||||
import './index.scss';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import local from 'utilities/local';
|
||||
import Request from 'kolide/request';
|
||||
import URL_PREFIX from 'router/url_prefix';
|
||||
|
||||
|
||||
class Base {
|
||||
constructor () {
|
||||
const { origin } = global.window.location;
|
||||
|
||||
this.baseURL = `${origin}/api`;
|
||||
this.baseURL = `${origin}${URL_PREFIX}/api`;
|
||||
this.bearerToken = local.getItem('auth_token');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import ResetPasswordForm from 'components/forms/ResetPasswordForm';
|
|||
import StackedWhiteBoxes from 'components/StackedWhiteBoxes';
|
||||
import { performRequiredPasswordReset } from 'redux/nodes/auth/actions';
|
||||
import userInterface from 'interfaces/user';
|
||||
import PATHS from 'router/paths';
|
||||
|
||||
export class ResetPasswordPage extends Component {
|
||||
static propTypes = {
|
||||
|
|
@ -30,7 +31,7 @@ export class ResetPasswordPage extends Component {
|
|||
const { dispatch, token, user } = this.props;
|
||||
|
||||
if (!user && !token) {
|
||||
return dispatch(push('/login'));
|
||||
return dispatch(push(PATHS.LOGIN));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -59,9 +60,7 @@ export class ResetPasswordPage extends Component {
|
|||
};
|
||||
|
||||
return dispatch(resetPassword(resetPasswordData))
|
||||
.then(() => {
|
||||
return dispatch(push('/login'));
|
||||
})
|
||||
.then(() => { return dispatch(push(PATHS.LOGIN)); })
|
||||
.catch(() => false);
|
||||
})
|
||||
|
||||
|
|
@ -77,7 +76,7 @@ export class ResetPasswordPage extends Component {
|
|||
const passwordUpdateParams = { password };
|
||||
|
||||
return dispatch(performRequiredPasswordReset(passwordUpdateParams))
|
||||
.then(() => { return dispatch(push('/')); })
|
||||
.then(() => { return dispatch(push(PATHS.HOME)); })
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import hostActions from 'redux/nodes/entities/hosts/actions';
|
|||
import labelActions from 'redux/nodes/entities/labels/actions';
|
||||
import { renderFlash } from 'redux/nodes/notifications/actions';
|
||||
import entityGetter from 'redux/utilities/entityGetter';
|
||||
import paths from 'router/paths';
|
||||
import PATHS from 'router/paths';
|
||||
import deepDifference from 'utilities/deep_difference';
|
||||
import iconClassForLabel from 'utilities/icon_class_for_label';
|
||||
import platformIconClass from 'utilities/platform_icon_class';
|
||||
|
|
@ -99,7 +99,7 @@ export class ManageHostsPage extends PureComponent {
|
|||
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(push(`/hosts/manage${NEW_LABEL_HASH}`));
|
||||
dispatch(push(`${PATHS.MANAGE_HOSTS}${NEW_LABEL_HASH}`));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ export class ManageHostsPage extends PureComponent {
|
|||
onCancelAddLabel = () => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(push('/hosts/manage'));
|
||||
dispatch(push(PATHS.MANAGE_HOSTS));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -168,7 +168,7 @@ export class ManageHostsPage extends PureComponent {
|
|||
evt.preventDefault();
|
||||
|
||||
const { dispatch } = this.props;
|
||||
const { MANAGE_HOSTS } = paths;
|
||||
const { MANAGE_HOSTS } = PATHS;
|
||||
const { slug } = selectedLabel;
|
||||
const nextLocation = slug === 'all-hosts' ? MANAGE_HOSTS : `${MANAGE_HOSTS}/${slug}`;
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ export class ManageHostsPage extends PureComponent {
|
|||
|
||||
return dispatch(labelActions.create(formData))
|
||||
.then(() => {
|
||||
dispatch(push('/hosts/manage'));
|
||||
dispatch(push(PATHS.MANAGE_HOSTS));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
|
@ -236,7 +236,7 @@ export class ManageHostsPage extends PureComponent {
|
|||
onDeleteLabel = () => {
|
||||
const { toggleDeleteLabelModal } = this;
|
||||
const { dispatch, selectedLabel } = this.props;
|
||||
const { MANAGE_HOSTS } = paths;
|
||||
const { MANAGE_HOSTS } = PATHS;
|
||||
|
||||
return dispatch(labelActions.destroy(selectedLabel))
|
||||
.then(() => {
|
||||
|
|
@ -251,7 +251,7 @@ export class ManageHostsPage extends PureComponent {
|
|||
evt.preventDefault();
|
||||
|
||||
const { dispatch } = this.props;
|
||||
const { NEW_QUERY } = paths;
|
||||
const { NEW_QUERY } = PATHS;
|
||||
|
||||
dispatch(push({
|
||||
pathname: NEW_QUERY,
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ import PackDetailsSidePanel from 'components/side_panels/PackDetailsSidePanel';
|
|||
import PackInfoSidePanel from 'components/side_panels/PackInfoSidePanel';
|
||||
import packInterface from 'interfaces/pack';
|
||||
import PacksList from 'components/packs/PacksList';
|
||||
import paths from 'router/paths';
|
||||
import { renderFlash } from 'redux/nodes/notifications/actions';
|
||||
import scheduledQueryActions from 'redux/nodes/entities/scheduled_queries/actions';
|
||||
import scheduledQueryInterface from 'interfaces/scheduled_query';
|
||||
import PATHS from 'router/paths';
|
||||
|
||||
|
||||
const baseClass = 'all-packs-page';
|
||||
|
||||
|
|
@ -137,7 +138,7 @@ export class AllPacksPage extends Component {
|
|||
onSelectPack = (selectedPack) => {
|
||||
const { dispatch } = this.props;
|
||||
const locationObject = {
|
||||
pathname: '/packs/manage',
|
||||
pathname: PATHS.MANAGE_PACKS,
|
||||
query: { selectedPack: selectedPack.id },
|
||||
};
|
||||
|
||||
|
|
@ -149,7 +150,7 @@ export class AllPacksPage extends Component {
|
|||
onDoubleClickPack = (selectedPack) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(push(`/packs/${selectedPack.id}`));
|
||||
dispatch(push(PATHS.PACK(selectedPack)));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -205,9 +206,8 @@ export class AllPacksPage extends Component {
|
|||
|
||||
goToNewPackPage = () => {
|
||||
const { dispatch } = this.props;
|
||||
const { NEW_PACK } = paths;
|
||||
|
||||
dispatch(push(NEW_PACK));
|
||||
dispatch(push(PATHS.NEW_PACK));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import ScheduledQueriesListWrapper from 'components/queries/ScheduledQueriesList
|
|||
import { renderFlash } from 'redux/nodes/notifications/actions';
|
||||
import scheduledQueryActions from 'redux/nodes/entities/scheduled_queries/actions';
|
||||
import stateEntityGetter from 'redux/utilities/entityGetter';
|
||||
import PATHS from 'router/paths';
|
||||
|
||||
const baseClass = 'edit-pack-page';
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ export class EditPackPage extends Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
return dispatch(push(`/packs/${packID}`));
|
||||
return dispatch(push(PATHS.PACK({ id: packID })));
|
||||
}
|
||||
|
||||
onFetchTargets = (query, targetsResponse) => {
|
||||
|
|
@ -145,17 +146,17 @@ export class EditPackPage extends Component {
|
|||
onDblClickScheduledQuery = (scheduledQueryId) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
return dispatch(push(`/queries/${scheduledQueryId}`));
|
||||
return dispatch(push(PATHS.EDIT_QUERY({ id: scheduledQueryId })));
|
||||
}
|
||||
|
||||
onToggleEdit = () => {
|
||||
const { dispatch, isEdit, packID } = this.props;
|
||||
|
||||
if (isEdit) {
|
||||
return dispatch(push(`/packs/${packID}`));
|
||||
return dispatch(push(PATHS.PACK({ id: packID })));
|
||||
}
|
||||
|
||||
return dispatch(push(`/packs/${packID}/edit`));
|
||||
return dispatch(push(PATHS.EDIT_PACK({ id: packID })));
|
||||
}
|
||||
|
||||
onUpdateScheduledQuery = (formData) => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { push } from 'react-router-redux';
|
|||
import packActions from 'redux/nodes/entities/packs/actions';
|
||||
import PackForm from 'components/forms/packs/PackForm';
|
||||
import PackInfoSidePanel from 'components/side_panels/PackInfoSidePanel';
|
||||
import PATHS from 'router/paths';
|
||||
|
||||
const baseClass = 'pack-composer';
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ export class PackComposerPage extends Component {
|
|||
visitPackPage = (packID) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(push(`/packs/${packID}`));
|
||||
dispatch(push(PATHS.PACK({ id: packID })));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import Modal from 'components/modals/Modal';
|
|||
import NumberPill from 'components/NumberPill';
|
||||
import Icon from 'components/icons/Icon';
|
||||
import PackInfoSidePanel from 'components/side_panels/PackInfoSidePanel';
|
||||
import paths from 'router/paths';
|
||||
import PATHS from 'router/paths';
|
||||
import QueryDetailsSidePanel from 'components/side_panels/QueryDetailsSidePanel';
|
||||
import QueriesList from 'components/queries/QueriesList';
|
||||
import queryActions from 'redux/nodes/entities/queries/actions';
|
||||
|
|
@ -114,7 +114,7 @@ export class ManageQueriesPage extends Component {
|
|||
onSelectQuery = (selectedQuery) => {
|
||||
const { dispatch } = this.props;
|
||||
const locationObject = {
|
||||
pathname: '/queries/manage',
|
||||
pathname: PATHS.MANAGE_QUERIES,
|
||||
query: { selectedQuery: selectedQuery.id },
|
||||
};
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ export class ManageQueriesPage extends Component {
|
|||
onDblClickQuery = (selectedQuery) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(push(`/queries/${selectedQuery.id}`));
|
||||
dispatch(push(PATHS.EDIT_QUERY(selectedQuery)));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -162,7 +162,7 @@ export class ManageQueriesPage extends Component {
|
|||
|
||||
goToNewQueryPage = () => {
|
||||
const { dispatch } = this.props;
|
||||
const { NEW_QUERY } = paths;
|
||||
const { NEW_QUERY } = PATHS;
|
||||
|
||||
dispatch(push(NEW_QUERY));
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ export class ManageQueriesPage extends Component {
|
|||
|
||||
goToEditQueryPage = (query) => {
|
||||
const { dispatch } = this.props;
|
||||
const { EDIT_QUERY } = paths;
|
||||
const { EDIT_QUERY } = PATHS;
|
||||
|
||||
dispatch(push(EDIT_QUERY(query)));
|
||||
|
||||
|
|
@ -308,4 +308,3 @@ const mapStateToProps = (state, { location }) => {
|
|||
};
|
||||
|
||||
export default connect(mapStateToProps)(ManageQueriesPage);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { toggleSmallNav } from 'redux/nodes/app/actions';
|
|||
import { selectOsqueryTable, setSelectedTargets, setSelectedTargetsQuery } from 'redux/nodes/components/QueryPages/actions';
|
||||
import targetInterface from 'interfaces/target';
|
||||
import validateQuery from 'components/forms/validators/validate_query';
|
||||
import PATHS from 'router/paths';
|
||||
|
||||
const baseClass = 'query-page';
|
||||
const DEFAULT_CAMPAIGN = {
|
||||
|
|
@ -280,7 +281,7 @@ export class QueryPage extends Component {
|
|||
|
||||
return dispatch(queryActions.create(formData))
|
||||
.then((query) => {
|
||||
dispatch(push(`/queries/${query.id}`));
|
||||
dispatch(push(PATHS.EDIT_QUERY(query)));
|
||||
})
|
||||
.catch(() => false);
|
||||
})
|
||||
|
|
|
|||
4
frontend/public-path.js
Normal file
4
frontend/public-path.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import URL_PREFIX from 'router/url_prefix';
|
||||
|
||||
// Sets the path used to load assets
|
||||
__webpack_public_path__ = `${URL_PREFIX}/assets/`; // eslint-disable-line camelcase, no-undef
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { browserHistory, IndexRedirect, IndexRoute, Redirect, Route, Router } from 'react-router';
|
||||
import { browserHistory, IndexRedirect, IndexRoute, Route, Router } from 'react-router';
|
||||
import { Provider } from 'react-redux';
|
||||
import { syncHistoryWithStore } from 'react-router-redux';
|
||||
|
||||
|
|
@ -27,13 +27,14 @@ import Kolide404 from 'pages/Kolide404';
|
|||
import Kolide500 from 'pages/Kolide500';
|
||||
import store from 'redux/store';
|
||||
import UserSettingsPage from 'pages/UserSettingsPage';
|
||||
import PATHS from 'router/paths';
|
||||
|
||||
const history = syncHistoryWithStore(browserHistory, store);
|
||||
|
||||
const routes = (
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<Route path="/" component={App}>
|
||||
<Route path={PATHS.HOME} component={App}>
|
||||
<Route path="setup" component={RegistrationPage} />
|
||||
<Route path="login" component={LoginRoutes}>
|
||||
<Route path="invites/:invite_token" component={ConfirmInvitePage} />
|
||||
|
|
@ -45,7 +46,7 @@ const routes = (
|
|||
<Route path="email/change/:token" component={EmailTokenRedirect} />
|
||||
<Route path="logout" component={LogoutPage} />
|
||||
<Route component={CoreLayout}>
|
||||
<IndexRedirect to="/hosts/manage" />
|
||||
<IndexRedirect to={PATHS.MANAGE_HOSTS} />
|
||||
<Route path="admin" component={AuthenticatedAdminRoutes}>
|
||||
<Route path="users" component={AdminUserManagementPage} />
|
||||
<Route path="settings" component={AdminAppSettingsPage} />
|
||||
|
|
@ -75,7 +76,7 @@ const routes = (
|
|||
</Route>
|
||||
<Route path="/500" component={Kolide500} />
|
||||
<Route path="/404" component={Kolide404} />
|
||||
<Redirect from="*" to="/404" />
|
||||
<Route component={Kolide404} />
|
||||
</Router>
|
||||
</Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,29 @@
|
|||
import URL_PREFIX from 'router/url_prefix';
|
||||
|
||||
export default {
|
||||
ADMIN_DASHBOARD: '/admin',
|
||||
ADMIN_SETTINGS: '/admin/settings',
|
||||
ALL_PACKS: '/packs/all',
|
||||
EDIT_QUERY: (query) => {
|
||||
return `/queries/${query.id}`;
|
||||
ADMIN_USERS: `${URL_PREFIX}/admin/users`,
|
||||
ADMIN_SETTINGS: `${URL_PREFIX}/admin/settings`,
|
||||
ALL_PACKS: `${URL_PREFIX}/packs/all`,
|
||||
EDIT_PACK: (pack) => {
|
||||
return `${URL_PREFIX}/packs/${pack.id}/edit`;
|
||||
},
|
||||
FORGOT_PASSWORD: '/login/forgot',
|
||||
HOME: '/',
|
||||
KOLIDE_500: '/500',
|
||||
LOGIN: '/login',
|
||||
LOGOUT: '/logout',
|
||||
MANAGE_HOSTS: '/hosts/manage',
|
||||
NEW_PACK: '/packs/new',
|
||||
NEW_QUERY: '/queries/new',
|
||||
RESET_PASSWORD: '/login/reset',
|
||||
SETUP: '/setup',
|
||||
USER_SETTINGS: '/settings',
|
||||
PACK: (pack) => {
|
||||
return `${URL_PREFIX}/packs/${pack.id}`;
|
||||
},
|
||||
EDIT_QUERY: (query) => {
|
||||
return `${URL_PREFIX}/queries/${query.id}`;
|
||||
},
|
||||
FORGOT_PASSWORD: `${URL_PREFIX}/login/forgot`,
|
||||
HOME: `${URL_PREFIX}/`,
|
||||
KOLIDE_500: `${URL_PREFIX}/500`,
|
||||
LOGIN: `${URL_PREFIX}/login`,
|
||||
LOGOUT: `${URL_PREFIX}/logout`,
|
||||
MANAGE_HOSTS: `${URL_PREFIX}/hosts/manage`,
|
||||
MANAGE_PACKS: `${URL_PREFIX}/packs/manage`,
|
||||
NEW_PACK: `${URL_PREFIX}/packs/new`,
|
||||
MANAGE_QUERIES: `${URL_PREFIX}/queries/manage`,
|
||||
NEW_QUERY: `${URL_PREFIX}/queries/new`,
|
||||
RESET_PASSWORD: `${URL_PREFIX}/login/reset`,
|
||||
SETUP: `${URL_PREFIX}/setup`,
|
||||
USER_SETTINGS: `${URL_PREFIX}/settings`,
|
||||
};
|
||||
|
|
|
|||
4
frontend/router/url_prefix.js
Normal file
4
frontend/router/url_prefix.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Encapsulate the URL Prefix so that this is the only module that
|
||||
// needs to access the global. All other modules should use this one.
|
||||
|
||||
export default window.urlPrefix || '';
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
@font-face {
|
||||
font-family: 'Oxygen';
|
||||
src: url('/assets/fonts/oxygen/Oxygen-Light.eot');
|
||||
src: url('/assets/fonts/oxygen/Oxygen-Light.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/fonts/oxygen/Oxygen-Light.woff') format('woff'),
|
||||
url('/assets/fonts/oxygen/Oxygen-Light.ttf') format('truetype');
|
||||
src: url('../assets/fonts/oxygen/Oxygen-Light.eot');
|
||||
src: url('../assets/fonts/oxygen/Oxygen-Light.eot?#iefix') format('embedded-opentype'),
|
||||
url('../assets/fonts/oxygen/Oxygen-Light.woff') format('woff'),
|
||||
url('../assets/fonts/oxygen/Oxygen-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Oxygen';
|
||||
src: url('/assets/fonts/oxygen/Oxygen-Bold.eot');
|
||||
src: url('/assets/fonts/oxygen/Oxygen-Bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/fonts/oxygen/Oxygen-Bold.woff') format('woff'),
|
||||
url('/assets/fonts/oxygen/Oxygen-Bold.ttf') format('truetype');
|
||||
src: url('../assets/fonts/oxygen/Oxygen-Bold.eot');
|
||||
src: url('../assets/fonts/oxygen/Oxygen-Bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('../assets/fonts/oxygen/Oxygen-Bold.woff') format('woff'),
|
||||
url('../assets/fonts/oxygen/Oxygen-Bold.ttf') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Oxygen';
|
||||
src: url('/assets/fonts/oxygen/Oxygen-Regular.eot');
|
||||
src: url('/assets/fonts/oxygen/Oxygen-Regular.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/fonts/oxygen/Oxygen-Regular.woff') format('woff'),
|
||||
url('/assets/fonts/oxygen/Oxygen-Regular.ttf') format('truetype');
|
||||
src: url('../assets/fonts/oxygen/Oxygen-Regular.eot');
|
||||
src: url('../assets/fonts/oxygen/Oxygen-Regular.eot?#iefix') format('embedded-opentype'),
|
||||
url('../assets/fonts/oxygen/Oxygen-Regular.woff') format('woff'),
|
||||
url('../assets/fonts/oxygen/Oxygen-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'kolidecons';
|
||||
src: url('/assets/fonts/kolidecons/kolidecons.woff2') format('woff2'),
|
||||
url('/assets/fonts/kolidecons/kolidecons.woff') format('woff');
|
||||
src: url('../assets/fonts/kolidecons/kolidecons.woff2') format('woff2'),
|
||||
url('../assets/fonts/kolidecons/kolidecons.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'SourceCodePro';
|
||||
src: url('/assets/fonts/source-code-pro/SourceCodePro-Regular.eot');
|
||||
src: url('/assets/fonts/source-code-pro/SourceCodePro-Regular.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/fonts/source-code-pro/SourceCodePro-Regular.ttf.woff') format('woff'),
|
||||
url('/assets/fonts/source-code-pro/SourceCodePro-Regular.ttf') format('truetype');
|
||||
src: url('../assets/fonts/source-code-pro/SourceCodePro-Regular.eot');
|
||||
src: url('../assets/fonts/source-code-pro/SourceCodePro-Regular.eot?#iefix') format('embedded-opentype'),
|
||||
url('../assets/fonts/source-code-pro/SourceCodePro-Regular.ttf.woff') format('woff'),
|
||||
url('../assets/fonts/source-code-pro/SourceCodePro-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal; }
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ a {
|
|||
margin: 0;
|
||||
|
||||
&--background {
|
||||
background: url('/assets/images/background.png') center center;
|
||||
background: url('../assets/images/background.png') center center;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html data-uuid="{{ .UUID }}">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="<%= htmlWebpackPlugin.files.css[0] %>">
|
||||
<link rel="stylesheet" type="text/css" href="{{.URLPrefix}}<%= htmlWebpackPlugin.files.css[0] %>">
|
||||
|
||||
<!-- Icons & Platform Specific Settings - Favicon generator used to generate the icons below http://realfavicongenerator.net -->
|
||||
<!-- shortcut icon - This file contains the following sizes 16x16, 32x32 and 48x48. -->
|
||||
<link rel="shortcut icon" href="/assets/favicons/favicon.ico">
|
||||
<link rel="shortcut icon" href="{{.URLPrefix}}/assets/favicons/favicon.ico">
|
||||
<!-- favicon-96x96.png - For Google TV https://developer.android.com/training/tv/index.html#favicons. -->
|
||||
<link rel="icon" type="image/png" href="/assets/favicons/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="{{.URLPrefix}}/assets/favicons/favicon-96x96.png" sizes="96x96">
|
||||
<!-- favicon-32x32.png - For Safari on Mac OS. -->
|
||||
<link rel="icon" type="image/png" href="/assets/favicons/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="{{.URLPrefix}}/assets/favicons/favicon-32x32.png" sizes="32x32">
|
||||
<!-- favicon-16x16.png - The classic favicon, displayed in the tabs. -->
|
||||
<link rel="icon" type="image/png" href="/assets/favicons/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="{{.URLPrefix}}/assets/favicons/favicon-16x16.png" sizes="16x16">
|
||||
|
||||
<!-- Android/Chrome -->
|
||||
<!-- manifest-json - The location of the browser configuration file. It contains locations of icon files, name of the application and default device screen orientation. Note that the name field is mandatory.
|
||||
https://developer.chrome.com/multidevice/android/installtohomescreen. -->
|
||||
<link rel="manifest" href="/assets/favicons/manifest.json">
|
||||
<link rel="manifest" href="{{.URLPrefix}}/assets/favicons/manifest.json">
|
||||
<!-- theme-color - The colour of the toolbar in Chrome M39+
|
||||
http://updates.html5rocks.com/2014/11/Support-for-theme-color-in-Chrome-39-for-Android -->
|
||||
<meta name="theme-color" content="#1E1E1E">
|
||||
|
|
@ -28,43 +28,46 @@
|
|||
<!-- Apple Icons - You can move all these icons to the root of the site and remove these link elements, if you don't mind the clutter.
|
||||
https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariHTMLRef/Introduction.html#//apple_ref/doc/uid/30001261-SW1 -->
|
||||
<!-- apple-touch-icon-57x57.png - Android Stock Browser and non-Retina iPhone and iPod Touch -->
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/assets/favicons/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-57x57.png">
|
||||
<!-- apple-touch-icon-114x114.png - iPhone (with 2× display) iOS = 6 -->
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/assets/favicons/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-114x114.png">
|
||||
<!-- apple-touch-icon-72x72.png - iPad mini and the first- and second-generation iPad (1× display) on iOS = 6 -->
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/assets/favicons/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-72x72.png">
|
||||
<!-- apple-touch-icon-144x144.png - iPad (with 2× display) iOS = 6 -->
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/assets/favicons/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-144x144.png">
|
||||
<!-- apple-touch-icon-60x60.png - Same as apple-touch-icon-57x57.png, for non-retina iPhone with iOS7. -->
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/assets/favicons/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-60x60.png">
|
||||
<!-- apple-touch-icon-120x120.png - iPhone (with 2× and 3 display) iOS = 7 -->
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/assets/favicons/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-120x120.png">
|
||||
<!-- apple-touch-icon-76x76.png - iPad mini and the first- and second-generation iPad (1× display) on iOS = 7 -->
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/assets/favicons/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-76x76.png">
|
||||
<!-- apple-touch-icon-152x152.png - iPad 3+ (with 2× display) iOS = 7 -->
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/assets/favicons/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-152x152.png">
|
||||
<!-- apple-touch-icon-180x180.png - iPad and iPad mini (with 2× display) iOS = 8 -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/favicons/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{.URLPrefix}}/assets/favicons/apple-touch-icon-180x180.png">
|
||||
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)" href="/assets/favicons/apple-touch-startup-image-320x460.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)" href="/assets/favicons/apple-touch-startup-image-640x920.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="/assets/favicons/apple-touch-startup-image-640x1096.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="/assets/favicons/apple-touch-startup-image-750x1294.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)" href="/assets/favicons/apple-touch-startup-image-1182x2208.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)" href="/assets/favicons/apple-touch-startup-image-1242x2148.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)" href="/assets/favicons/apple-touch-startup-image-748x1024.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)" href="/assets/favicons/apple-touch-startup-image-768x1004.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)" href="/assets/favicons/apple-touch-startup-image-1496x2048.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)" href="/assets/favicons/apple-touch-startup-image-1536x2008.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-320x460.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-640x920.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-640x1096.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-750x1294.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-1182x2208.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-1242x2148.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-748x1024.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-768x1004.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-1496x2048.png"/>
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)" href="{{.URLPrefix}}/assets/favicons/apple-touch-startup-image-1536x2008.png"/>
|
||||
<!-- Windows 8.1 IE11 -->
|
||||
<!-- msapplication-config - The location of the browser configuration file. If you have an RSS feed, go to
|
||||
http://www.buildmypinnedsite.com and regenerate the browserconfig.xml file. You will then have a cool live tile! -->
|
||||
<meta name="msapplication-config" content="/assets/favicons/browserconfig.xml">
|
||||
<meta name="msapplication-config" content="{{.URLPrefix}}/assets/favicons/browserconfig.xml">
|
||||
<title>Kolide Fleet</title>
|
||||
<script type="text/javascript">
|
||||
var urlPrefix = "{{.URLPrefix}}";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script async defer src="<%= htmlWebpackPlugin.files.js[0] %>" onload="this.parentElement.removeChild(this)"></script>
|
||||
<script async defer src="{{.URLPrefix}}<%= htmlWebpackPlugin.files.js[0] %>" onload="this.parentElement.removeChild(this)"></script>
|
||||
<!-- Because iOS hates interactive stuff, we have to kill it with fire -->
|
||||
<script>document.addEventListener("touchstart", function() {},false);</script>
|
||||
<!-- End Apple Hate -->
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ type ServerConfig struct {
|
|||
Key string
|
||||
TLS bool
|
||||
TLSProfile string
|
||||
URLPrefix string `yaml:"url_prefix"`
|
||||
}
|
||||
|
||||
// AuthConfig defines configs related to user authorization
|
||||
|
|
@ -173,6 +174,8 @@ func (man Manager) addConfigs() {
|
|||
man.addConfigString(TLSProfileKey, TLSProfileModern,
|
||||
fmt.Sprintf("TLS security profile choose one of %s, %s or %s",
|
||||
TLSProfileModern, TLSProfileIntermediate, TLSProfileOld))
|
||||
man.addConfigString("server.url_prefix", "",
|
||||
"URL prefix used on server and frontend endpoints")
|
||||
|
||||
// Auth
|
||||
man.addConfigString("auth.jwt_key", "",
|
||||
|
|
@ -272,6 +275,7 @@ func (man Manager) LoadConfig() KolideConfig {
|
|||
Key: man.getConfigString("server.key"),
|
||||
TLS: man.getConfigBool("server.tls"),
|
||||
TLSProfile: man.getConfigTLSProfile(),
|
||||
URLPrefix: man.getConfigString("server.url_prefix"),
|
||||
},
|
||||
Auth: AuthConfig{
|
||||
JwtKey: man.getConfigString("auth.jwt_key"),
|
||||
|
|
|
|||
|
|
@ -90,8 +90,7 @@ func testOptionsToConfig(t *testing.T, ds kolide.Datastore) {
|
|||
require.Nil(t, ds.MigrateData())
|
||||
resp, err := ds.GetOsqueryConfigOptions()
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, resp, 10)
|
||||
assert.Equal(t, "/api/v1/osquery/distributed/read", resp["distributed_tls_read_endpoint"])
|
||||
assert.Len(t, resp, 8)
|
||||
opt, _ := ds.OptionByName("aws_profile_name")
|
||||
assert.False(t, opt.OptionSet())
|
||||
opt.SetValue("zip")
|
||||
|
|
@ -99,7 +98,7 @@ func testOptionsToConfig(t *testing.T, ds kolide.Datastore) {
|
|||
require.Nil(t, err)
|
||||
resp, err = ds.GetOsqueryConfigOptions()
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, resp, 11)
|
||||
assert.Len(t, resp, 9)
|
||||
assert.Equal(t, "zip", resp["aws_profile_name"])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ func Options() []struct {
|
|||
// raise an error
|
||||
{"disable_distributed", false, kolide.OptionTypeBool, kolide.ReadOnly},
|
||||
{"distributed_plugin", "tls", kolide.OptionTypeString, kolide.ReadOnly},
|
||||
{"distributed_tls_read_endpoint", "/api/v1/osquery/distributed/read", kolide.OptionTypeString, kolide.ReadOnly},
|
||||
{"distributed_tls_write_endpoint", "/api/v1/osquery/distributed/write", kolide.OptionTypeString, kolide.ReadOnly},
|
||||
{"pack_delimiter", "/", kolide.OptionTypeString, kolide.ReadOnly},
|
||||
// These options may be modified by an admin user
|
||||
{"aws_access_key_id", nil, kolide.OptionTypeString, kolide.NotReadOnly},
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ type PasswordResetRequest struct {
|
|||
// SMTPTestMailer is used to build an email message that will be used as
|
||||
// a test message when testing SMTP configuration
|
||||
type SMTPTestMailer struct {
|
||||
KolideServerURL template.URL
|
||||
AssetURL template.URL
|
||||
BaseURL template.URL
|
||||
AssetURL template.URL
|
||||
}
|
||||
|
||||
func (m *SMTPTestMailer) Message() ([]byte, error) {
|
||||
|
|
@ -68,8 +68,8 @@ func (m *SMTPTestMailer) Message() ([]byte, error) {
|
|||
}
|
||||
|
||||
type PasswordResetMailer struct {
|
||||
// URL for the Fleet application
|
||||
KolideServerURL template.URL
|
||||
// Base URL to use for Fleet endpoints
|
||||
BaseURL template.URL
|
||||
// URL for loading image assets
|
||||
AssetURL template.URL
|
||||
// Token password reset token
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import (
|
|||
|
||||
func TestTemplateProcessor(t *testing.T) {
|
||||
mailer := PasswordResetMailer{
|
||||
KolideServerURL: "https://localhost.com:8080",
|
||||
Token: "12345",
|
||||
BaseURL: "https://localhost.com:8080",
|
||||
Token: "12345",
|
||||
}
|
||||
|
||||
out, err := mailer.Message()
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ type Invite struct {
|
|||
// InviteMailer is used to build an email template for the invite email.
|
||||
type InviteMailer struct {
|
||||
*Invite
|
||||
KolideServerURL template.URL
|
||||
BaseURL template.URL
|
||||
AssetURL template.URL
|
||||
InvitedByUsername string
|
||||
OrgName string
|
||||
|
|
|
|||
|
|
@ -194,9 +194,9 @@ func falseIfNil(b *bool) bool {
|
|||
}
|
||||
|
||||
type ChangeEmailMailer struct {
|
||||
KolideServerURL template.URL
|
||||
AssetURL template.URL
|
||||
Token string
|
||||
BaseURL template.URL
|
||||
AssetURL template.URL
|
||||
Token string
|
||||
}
|
||||
|
||||
func (cem *ChangeEmailMailer) Message() ([]byte, error) {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ func testSMTPPlainAuth(t *testing.T, mailer kolide.MailService) {
|
|||
SMTPSenderAddress: "kolide@kolide.com",
|
||||
},
|
||||
Mailer: &kolide.SMTPTestMailer{
|
||||
KolideServerURL: "https://localhost:8080",
|
||||
BaseURL: "https://localhost:8080",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ func testSMTPSkipVerify(t *testing.T, mailer kolide.MailService) {
|
|||
SMTPSenderAddress: "kolide@kolide.com",
|
||||
},
|
||||
Mailer: &kolide.SMTPTestMailer{
|
||||
KolideServerURL: "https://localhost:8080",
|
||||
BaseURL: "https://localhost:8080",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ func testSMTPNoAuth(t *testing.T, mailer kolide.MailService) {
|
|||
SMTPSenderAddress: "kolide@kolide.com",
|
||||
},
|
||||
Mailer: &kolide.SMTPTestMailer{
|
||||
KolideServerURL: "https://localhost:8080",
|
||||
BaseURL: "https://localhost:8080",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ func testMailTest(t *testing.T, mailer kolide.MailService) {
|
|||
SMTPSenderAddress: "kolide@kolide.com",
|
||||
},
|
||||
Mailer: &kolide.SMTPTestMailer{
|
||||
KolideServerURL: "https://localhost:8080",
|
||||
BaseURL: "https://localhost:8080",
|
||||
},
|
||||
}
|
||||
err := Test(mailer, mail)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
<table bgcolor="#f4f6fb" height="100px" cellpadding="20px">
|
||||
<tr>
|
||||
<td style="font-family: 'Oxygen', Arial, sans-serif;">
|
||||
<a href="{{.KolideServerURL}}/email/change/{{.Token}}">{{.KolideServerURL}}/email/change/{{.Token}}</a>
|
||||
<a href="{{.BaseURL}}/email/change/{{.Token}}">{{.BaseURL}}/email/change/{{.Token}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@
|
|||
<tr>
|
||||
<td style="font-family: 'Oxygen', Arial, sans-serif;">
|
||||
{{if .SSOEnabled}}
|
||||
<a href="{{.KolideServerURL}}/login/ssoinvites/{{.Token}}?name={{.Name}}&email={{.Email}}">{{.KolideServerURL}}/login/ssoinvites/{{.Token}}?name={{.Name}}&email={{.Email}}</a>
|
||||
<a href="{{.BaseURL}}/login/ssoinvites/{{.Token}}?name={{.Name}}&email={{.Email}}">{{.BaseURL}}/login/ssoinvites/{{.Token}}?name={{.Name}}&email={{.Email}}</a>
|
||||
{{else}}
|
||||
<a href="{{.KolideServerURL}}/login/invites/{{.Token}}?name={{.Name}}&email={{.Email}}">{{.KolideServerURL}}/login/invites/{{.Token}}?name={{.Name}}&email={{.Email}}</a>
|
||||
<a href="{{.BaseURL}}/login/invites/{{.Token}}?name={{.Name}}&email={{.Email}}">{{.BaseURL}}/login/invites/{{.Token}}?name={{.Name}}&email={{.Email}}</a>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
<td colspan="2" style="padding:60px; font-family: 'Oxygen', Arial, sans-serif;">
|
||||
<h1 style="font-weight:300">Reset Your Fleet Password...</h1>
|
||||
<p>Someone requested a password reset on your Fleet account. Follow the link below to reset your password:</p>
|
||||
<p><a href="{{.KolideServerURL}}/login/reset?token={{.Token}}">Reset Password</a></p>
|
||||
<p><a href="{{.BaseURL}}/login/reset?token={{.Token}}">Reset Password</a></p>
|
||||
<p style="color:#9ca3ac"><em>If you did not make the request, you may ignore this email as no changes have been made.</em></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
<tr>
|
||||
<td colspan="2" style="padding:60px; font-family: 'Oxygen', Arial, sans-serif;">
|
||||
<h1 style="font-weight:300">Confirmed Fleet SMTP Setup</h1>
|
||||
<p>This message confirms that SMTP is set up properly on your <a href="{{.KolideServerURL}}">Fleet instance</a>.</p>
|
||||
<p>This message confirms that SMTP is set up properly on your <a href="{{.BaseURL}}">Fleet instance</a>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr bgcolor="#9ca3ac">
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@ import (
|
|||
type Client struct {
|
||||
addr string
|
||||
baseURL *url.URL
|
||||
urlPrefix string
|
||||
token string
|
||||
http *http.Client
|
||||
insecureSkipVerify bool
|
||||
}
|
||||
|
||||
func NewClient(addr string, insecureSkipVerify bool, rootCA string) (*Client, error) {
|
||||
func NewClient(addr string, insecureSkipVerify bool, rootCA, urlPrefix string) (*Client, error) {
|
||||
if !strings.HasPrefix(addr, "https://") {
|
||||
return nil, errors.New("Address must start with https://")
|
||||
}
|
||||
|
|
@ -67,6 +68,7 @@ func NewClient(addr string, insecureSkipVerify bool, rootCA string) (*Client, er
|
|||
baseURL: baseURL,
|
||||
http: httpClient,
|
||||
insecureSkipVerify: insecureSkipVerify,
|
||||
urlPrefix: urlPrefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -124,6 +126,6 @@ func (c *Client) SetToken(t string) {
|
|||
|
||||
func (c *Client) url(path string) *url.URL {
|
||||
u := *c.baseURL
|
||||
u.Path = path
|
||||
u.Path = c.urlPrefix + path
|
||||
return &u
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func (c *Client) LiveQuery(query string, labels []string, hosts []string) (*Live
|
|||
|
||||
wssURL := *c.baseURL
|
||||
wssURL.Scheme = "wss"
|
||||
wssURL.Path = "/api/v1/kolide/results/websocket"
|
||||
wssURL.Path = c.urlPrefix + "/api/v1/kolide/results/websocket"
|
||||
conn, _, err := dialer.Dial(wssURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "upgrade live query result websocket")
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ func (r callbackSSOResponse) error() error { return r.Err }
|
|||
// If html is present we return a web page
|
||||
func (r callbackSSOResponse) html() string { return r.content }
|
||||
|
||||
func makeCallbackSSOEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
func makeCallbackSSOEndpoint(svc kolide.Service, urlPrefix string) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
authResponse := request.(kolide.Auth)
|
||||
session, err := svc.CallbackSSO(ctx, authResponse)
|
||||
|
|
@ -216,7 +216,7 @@ func makeCallbackSSOEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
// redirect to login page on front end if there was some problem,
|
||||
// errors should still be logged
|
||||
session = &kolide.SSOSession{
|
||||
RedirectURL: "/login",
|
||||
RedirectURL: urlPrefix + "/login",
|
||||
Token: "",
|
||||
}
|
||||
resp.Err = err
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ func setupEndpointTest(t *testing.T) *testResource {
|
|||
logger := kitlog.NewLogfmtLogger(os.Stdout)
|
||||
jwtKey := "CHANGEME"
|
||||
|
||||
routes := MakeHandler(svc, jwtKey, logger)
|
||||
routes := MakeHandler(svc, config.KolideConfig{Auth: config.AuthConfig{JwtKey: jwtKey}}, logger)
|
||||
|
||||
test.server = httptest.NewServer(routes)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func newBinaryFileSystem(root string) *assetfs.AssetFS {
|
|||
}
|
||||
}
|
||||
|
||||
func ServeFrontend(logger log.Logger) http.Handler {
|
||||
func ServeFrontend(urlPrefix string, logger log.Logger) http.Handler {
|
||||
herr := func(w http.ResponseWriter, err string) {
|
||||
logger.Log("err", err)
|
||||
http.Error(w, err, http.StatusInternalServerError)
|
||||
|
|
@ -40,7 +40,7 @@ func ServeFrontend(logger log.Logger) http.Handler {
|
|||
herr(w, "create react template: "+err.Error())
|
||||
return
|
||||
}
|
||||
if err := t.Execute(w, nil); err != nil {
|
||||
if err := t.Execute(w, struct{ URLPrefix string }{urlPrefix}); err != nil {
|
||||
herr(w, "execute react template: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
kitlog "github.com/go-kit/kit/log"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/kolide/fleet/server/config"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
|
@ -100,7 +101,7 @@ type KolideEndpoints struct {
|
|||
}
|
||||
|
||||
// MakeKolideServerEndpoints creates the Kolide API endpoints.
|
||||
func MakeKolideServerEndpoints(svc kolide.Service, jwtKey string) KolideEndpoints {
|
||||
func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) KolideEndpoints {
|
||||
return KolideEndpoints{
|
||||
Login: makeLoginEndpoint(svc),
|
||||
Logout: makeLogoutEndpoint(svc),
|
||||
|
|
@ -109,7 +110,7 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey string) KolideEndpoint
|
|||
CreateUser: makeCreateUserEndpoint(svc),
|
||||
VerifyInvite: makeVerifyInviteEndpoint(svc),
|
||||
InitiateSSO: makeInitiateSSOEndpoint(svc),
|
||||
CallbackSSO: makeCallbackSSOEndpoint(svc),
|
||||
CallbackSSO: makeCallbackSSOEndpoint(svc, urlPrefix),
|
||||
SSOSettings: makeSSOSettingsEndpoint(svc),
|
||||
|
||||
// Authenticated user endpoints
|
||||
|
|
@ -377,11 +378,11 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
|
|||
}
|
||||
|
||||
// MakeHandler creates an HTTP handler for the Fleet server endpoints.
|
||||
func MakeHandler(svc kolide.Service, jwtKey string, logger kitlog.Logger) http.Handler {
|
||||
func MakeHandler(svc kolide.Service, config config.KolideConfig, logger kitlog.Logger) http.Handler {
|
||||
kolideAPIOptions := []kithttp.ServerOption{
|
||||
kithttp.ServerBefore(
|
||||
kithttp.PopulateRequestContext, // populate the request context with common fields
|
||||
setRequestsContexts(svc, jwtKey),
|
||||
setRequestsContexts(svc, config.Auth.JwtKey),
|
||||
),
|
||||
kithttp.ServerErrorLogger(logger),
|
||||
kithttp.ServerErrorEncoder(encodeError),
|
||||
|
|
@ -390,7 +391,7 @@ func MakeHandler(svc kolide.Service, jwtKey string, logger kitlog.Logger) http.H
|
|||
),
|
||||
}
|
||||
|
||||
kolideEndpoints := MakeKolideServerEndpoints(svc, jwtKey)
|
||||
kolideEndpoints := MakeKolideServerEndpoints(svc, config.Auth.JwtKey, config.Server.URLPrefix)
|
||||
kolideHandlers := makeKolideKitHandlers(kolideEndpoints, kolideAPIOptions)
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
|
@ -398,7 +399,7 @@ func MakeHandler(svc kolide.Service, jwtKey string, logger kitlog.Logger) http.H
|
|||
addMetrics(r)
|
||||
|
||||
r.PathPrefix("/api/v1/kolide/results/").
|
||||
Handler(makeStreamDistributedQueryCampaignResultsHandler(svc, jwtKey, logger)).
|
||||
Handler(makeStreamDistributedQueryCampaignResultsHandler(svc, config.Auth.JwtKey, logger)).
|
||||
Name("distributed_query_results")
|
||||
|
||||
return r
|
||||
|
|
@ -543,7 +544,7 @@ func WithSetup(svc kolide.Service, logger kitlog.Logger, next http.Handler) http
|
|||
|
||||
// RedirectLoginToSetup detects if the setup endpoint should be used. If setup is required it redirect all
|
||||
// frontend urls to /setup, otherwise the frontend router is used.
|
||||
func RedirectLoginToSetup(svc kolide.Service, logger kitlog.Logger, next http.Handler) http.HandlerFunc {
|
||||
func RedirectLoginToSetup(svc kolide.Service, logger kitlog.Logger, next http.Handler, urlPrefix string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
redirect := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/setup" {
|
||||
|
|
@ -551,7 +552,7 @@ func RedirectLoginToSetup(svc kolide.Service, logger kitlog.Logger, next http.Ha
|
|||
return
|
||||
}
|
||||
newURL := r.URL
|
||||
newURL.Path = "/setup"
|
||||
newURL.Path = urlPrefix + "/setup"
|
||||
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
|
||||
})
|
||||
|
||||
|
|
@ -565,7 +566,7 @@ func RedirectLoginToSetup(svc kolide.Service, logger kitlog.Logger, next http.Ha
|
|||
redirect.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
RedirectSetupToLogin(svc, logger, next).ServeHTTP(w, r)
|
||||
RedirectSetupToLogin(svc, logger, next, urlPrefix).ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -584,11 +585,11 @@ func RequireSetup(svc kolide.Service) (bool, error) {
|
|||
|
||||
// RedirectSetupToLogin forces the /setup path to be redirected to login. This middleware is used after
|
||||
// the app has been setup.
|
||||
func RedirectSetupToLogin(svc kolide.Service, logger kitlog.Logger, next http.Handler) http.HandlerFunc {
|
||||
func RedirectSetupToLogin(svc kolide.Service, logger kitlog.Logger, next http.Handler, urlPrefix string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/setup" {
|
||||
newURL := r.URL
|
||||
newURL.Path = "/login"
|
||||
newURL.Path = urlPrefix + "/login"
|
||||
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func TestAPIRoutes(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
r := mux.NewRouter()
|
||||
ke := MakeKolideServerEndpoints(svc, "CHANGEME")
|
||||
ke := MakeKolideServerEndpoints(svc, "CHANGEME", "")
|
||||
kh := makeKolideKitHandlers(ke, nil)
|
||||
attachKolideAPIRoutes(r, kh)
|
||||
handler := mux.NewRouter()
|
||||
|
|
@ -242,7 +242,7 @@ func TestModifyUserPermissions(t *testing.T) {
|
|||
svc, err := newTestService(ms, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
handler := MakeHandler(svc, "CHANGEME", log.NewNopLogger())
|
||||
handler := MakeHandler(svc, config.KolideConfig{Auth: config.AuthConfig{JwtKey: "CHANGEME"}}, log.NewNopLogger())
|
||||
|
||||
testCases := []struct {
|
||||
ActingUserID uint
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func TestLogin(t *testing.T) {
|
|||
),
|
||||
}
|
||||
r := mux.NewRouter()
|
||||
ke := MakeKolideServerEndpoints(svc, "CHANGEME")
|
||||
ke := MakeKolideServerEndpoints(svc, "CHANGEME", "")
|
||||
kh := makeKolideKitHandlers(ke, opts)
|
||||
attachKolideAPIRoutes(r, kh)
|
||||
r.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ func (svc service) SendTestEmail(ctx context.Context, config *kolide.AppConfig)
|
|||
Subject: "Hello from Fleet",
|
||||
To: []string{vc.User.Email},
|
||||
Mailer: &kolide.SMTPTestMailer{
|
||||
KolideServerURL: template.URL(config.KolideServerURL),
|
||||
AssetURL: getAssetURL(),
|
||||
BaseURL: template.URL(config.KolideServerURL + svc.config.Server.URLPrefix),
|
||||
AssetURL: getAssetURL(),
|
||||
},
|
||||
Config: config,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func (svc service) InviteNewUser(ctx context.Context, payload kolide.InvitePaylo
|
|||
Config: config,
|
||||
Mailer: &kolide.InviteMailer{
|
||||
Invite: invite,
|
||||
KolideServerURL: template.URL(config.KolideServerURL),
|
||||
BaseURL: template.URL(config.KolideServerURL + svc.config.Server.URLPrefix),
|
||||
AssetURL: getAssetURL(),
|
||||
OrgName: config.OrgName,
|
||||
InvitedByUsername: invitedBy,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func (svc service) InitiateSSO(ctx context.Context, redirectURL string) (string,
|
|||
settings := sso.Settings{
|
||||
Metadata: metadata,
|
||||
// Construct call back url to send to idp
|
||||
AssertionConsumerServiceURL: appConfig.KolideServerURL + "/api/v1/kolide/sso/callback",
|
||||
AssertionConsumerServiceURL: appConfig.KolideServerURL + svc.config.Server.URLPrefix + "/api/v1/kolide/sso/callback",
|
||||
SessionStore: svc.ssoSessionStore,
|
||||
OriginalURL: redirectURL,
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ func (svc service) CallbackSSO(ctx context.Context, auth kolide.Auth) (*kolide.S
|
|||
RedirectURL: sess.OriginalURL,
|
||||
}
|
||||
if !strings.HasPrefix(result.RedirectURL, "/") {
|
||||
result.RedirectURL = "/" + result.RedirectURL
|
||||
result.RedirectURL = svc.config.Server.URLPrefix + result.RedirectURL
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,9 +153,9 @@ func (svc service) modifyEmailAddress(ctx context.Context, user *kolide.User, em
|
|||
To: []string{email},
|
||||
Config: config,
|
||||
Mailer: &kolide.ChangeEmailMailer{
|
||||
Token: token,
|
||||
KolideServerURL: template.URL(config.KolideServerURL),
|
||||
AssetURL: getAssetURL(),
|
||||
Token: token,
|
||||
BaseURL: template.URL(config.KolideServerURL + svc.config.Server.URLPrefix),
|
||||
AssetURL: getAssetURL(),
|
||||
},
|
||||
}
|
||||
return svc.mailService.SendEmail(changeEmail)
|
||||
|
|
@ -355,9 +355,9 @@ func (svc service) RequestPasswordReset(ctx context.Context, email string) error
|
|||
To: []string{user.Email},
|
||||
Config: config,
|
||||
Mailer: &kolide.PasswordResetMailer{
|
||||
KolideServerURL: template.URL(config.KolideServerURL),
|
||||
AssetURL: getAssetURL(),
|
||||
Token: token,
|
||||
BaseURL: template.URL(config.KolideServerURL + svc.config.Server.URLPrefix),
|
||||
AssetURL: getAssetURL(),
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,16 @@ var config = {
|
|||
noParse: /node_modules\/sqlite-parser\/dist\/sqlite-parser-min.js/,
|
||||
rules: [
|
||||
{ test: /\.(png|gif)$/, use: { loader: 'url-loader?name=[name]@[hash].[ext]&limit=6000' } },
|
||||
{ test: /\.(pdf|ico|jpg|svg|eot|otf|woff|ttf|mp4|webm)$/, use: { loader: 'file-loader?name=[name]@[hash].[ext]' } },
|
||||
{
|
||||
test: /\.(pdf|ico|jpg|svg|eot|otf|woff|woff2|ttf|mp4|webm)$/,
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name]@[hash].[ext]',
|
||||
useRelativePath: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ test: /\.tsx?$/, exclude: /node_modules/, use: { loader: 'ts-loader' } },
|
||||
{
|
||||
test: /\.scss$/,
|
||||
|
|
@ -65,6 +74,7 @@ var config = {
|
|||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
publicPath: './',
|
||||
hmr: process.env.NODE_ENV == 'development',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue