mirror of
https://github.com/gensokyo-zone/infrastructure.git
synced 2026-02-09 04:19:19 -08:00
chore(vouch): clean up local access
This commit is contained in:
parent
e4596f256f
commit
9274618cf0
9 changed files with 208 additions and 89 deletions
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkIf mkBefore;
|
|
||||||
inherit (lib.options) mkOption mkEnableOption;
|
|
||||||
inherit (lib.strings) concatMapStringsSep optionalString;
|
|
||||||
inherit (lib.lists) optionals;
|
|
||||||
inherit (config.services) tailscale;
|
|
||||||
inherit (config.networking.access) cidrForNetwork localaddrs;
|
|
||||||
localModule = { config, ... }: {
|
|
||||||
options = with lib.types; {
|
|
||||||
local = {
|
|
||||||
enable = mkEnableOption "local traffic only";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = mkIf config.local.enable {
|
|
||||||
extraConfig = let
|
|
||||||
mkAllow = cidr: "allow ${cidr};";
|
|
||||||
allowAddresses =
|
|
||||||
cidrForNetwork.loopback.all
|
|
||||||
++ cidrForNetwork.local.all
|
|
||||||
++ optionals tailscale.enable cidrForNetwork.tail.all;
|
|
||||||
allows = concatMapStringsSep "\n" mkAllow allowAddresses + optionalString localaddrs.enable ''
|
|
||||||
include ${localaddrs.stateDir}/*.nginx.conf;
|
|
||||||
'';
|
|
||||||
in mkBefore ''
|
|
||||||
${allows}
|
|
||||||
deny all;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
hostModule = { config, ... }: {
|
|
||||||
imports = [ localModule ];
|
|
||||||
|
|
||||||
options = with lib.types; {
|
|
||||||
locations = mkOption {
|
|
||||||
type = attrsOf (submodule localModule);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
options = with lib.types; {
|
|
||||||
services.nginx.virtualHosts = mkOption {
|
|
||||||
type = attrsOf (submodule hostModule);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
94
modules/nixos/nginx/local.nix
Normal file
94
modules/nixos/nginx/local.nix
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (lib.options) mkOption;
|
||||||
|
inherit (lib.modules) mkIf mkBefore mkOptionDefault;
|
||||||
|
inherit (lib.strings) concatMapStringsSep optionalString;
|
||||||
|
inherit (lib.lists) optionals;
|
||||||
|
inherit (config.services) tailscale;
|
||||||
|
inherit (config.networking.access) cidrForNetwork localaddrs;
|
||||||
|
localModule = { config, ... }: {
|
||||||
|
options.local = with lib.types; {
|
||||||
|
enable = mkOption {
|
||||||
|
type = bool;
|
||||||
|
description = "for local traffic only";
|
||||||
|
defaultText = literalExpression "false";
|
||||||
|
};
|
||||||
|
denyGlobal = mkOption {
|
||||||
|
type = bool;
|
||||||
|
defaultText = literalExpression "config.local.enable";
|
||||||
|
};
|
||||||
|
trusted = mkOption {
|
||||||
|
type = bool;
|
||||||
|
defaultText = literalExpression "config.local.denyGlobal";
|
||||||
|
};
|
||||||
|
emitDenyGlobal = mkOption {
|
||||||
|
internal = true;
|
||||||
|
type = bool;
|
||||||
|
default = config.local.denyGlobal;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = mkIf config.local.emitDenyGlobal {
|
||||||
|
extraConfig = let
|
||||||
|
mkAllow = cidr: "allow ${cidr};";
|
||||||
|
allowAddresses =
|
||||||
|
cidrForNetwork.loopback.all
|
||||||
|
++ cidrForNetwork.local.all
|
||||||
|
++ optionals tailscale.enable cidrForNetwork.tail.all;
|
||||||
|
allows = concatMapStringsSep "\n" mkAllow allowAddresses + optionalString localaddrs.enable ''
|
||||||
|
include ${localaddrs.stateDir}/*.nginx.conf;
|
||||||
|
'';
|
||||||
|
in mkBefore ''
|
||||||
|
${allows}
|
||||||
|
deny all;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
locationModule = { config, virtualHost, ... }: {
|
||||||
|
imports = [
|
||||||
|
localModule
|
||||||
|
];
|
||||||
|
|
||||||
|
config.local = {
|
||||||
|
enable = mkOptionDefault virtualHost.local.enable;
|
||||||
|
denyGlobal = mkOptionDefault virtualHost.local.denyGlobal;
|
||||||
|
trusted = mkOptionDefault virtualHost.local.trusted;
|
||||||
|
emitDenyGlobal = virtualHost.local.emitDenyGlobal;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hostModule = { config, ... }: {
|
||||||
|
imports = [ localModule ];
|
||||||
|
|
||||||
|
options = with lib.types; {
|
||||||
|
locations = mkOption {
|
||||||
|
type = attrsOf (submoduleWith {
|
||||||
|
modules = [ locationModule ];
|
||||||
|
shorthandOnlyDefinesConfig = true;
|
||||||
|
specialArgs = {
|
||||||
|
virtualHost = config;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.local = {
|
||||||
|
enable = mkOptionDefault false;
|
||||||
|
denyGlobal = mkOptionDefault config.local.enable;
|
||||||
|
trusted = mkOptionDefault config.local.denyGlobal;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options = with lib.types; {
|
||||||
|
services.nginx.virtualHosts = mkOption {
|
||||||
|
type = attrsOf (submoduleWith {
|
||||||
|
modules = [ hostModule ];
|
||||||
|
shorthandOnlyDefinesConfig = true;
|
||||||
|
specialArgs = {
|
||||||
|
nixosConfig = config;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,10 @@ let
|
||||||
type = str;
|
type = str;
|
||||||
default = "https://login.local.${networking.domain}";
|
default = "https://login.local.${networking.domain}";
|
||||||
};
|
};
|
||||||
|
doubleProxy = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
authUrl = mkOption {
|
authUrl = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
default = "https://id.${networking.domain}";
|
default = "https://id.${networking.domain}";
|
||||||
|
|
@ -49,6 +53,7 @@ let
|
||||||
in mkDefault "http://${host}:${toString port}";
|
in mkDefault "http://${host}:${toString port}";
|
||||||
authUrl = mkDefault vouch-proxy.authUrl;
|
authUrl = mkDefault vouch-proxy.authUrl;
|
||||||
url = mkDefault vouch-proxy.url;
|
url = mkDefault vouch-proxy.url;
|
||||||
|
doubleProxy = mkDefault false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
@ -63,11 +68,21 @@ let
|
||||||
'';
|
'';
|
||||||
locations = {
|
locations = {
|
||||||
"/" = {
|
"/" = {
|
||||||
extraConfig = ''
|
extraConfig = mkMerge [
|
||||||
add_header Access-Control-Allow-Origin ${config.vouch.url};
|
''
|
||||||
add_header Access-Control-Allow-Origin ${config.vouch.authUrl};
|
add_header Access-Control-Allow-Origin ${config.vouch.url};
|
||||||
proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
|
add_header Access-Control-Allow-Origin ${config.vouch.authUrl};
|
||||||
'';
|
''
|
||||||
|
(mkIf config.local.enable ''
|
||||||
|
add_header Access-Control-Allow-Origin ${config.vouch.localUrl};
|
||||||
|
'')
|
||||||
|
(mkIf (config.local.enable && tailscale.enable) ''
|
||||||
|
add_header Access-Control-Allow-Origin $scheme://${config.vouch.tailDomain};
|
||||||
|
'')
|
||||||
|
''
|
||||||
|
proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
|
||||||
|
''
|
||||||
|
];
|
||||||
};
|
};
|
||||||
"@error401" = {
|
"@error401" = {
|
||||||
extraConfig = let
|
extraConfig = let
|
||||||
|
|
@ -78,32 +93,45 @@ let
|
||||||
'';
|
'';
|
||||||
tailVouchUrl = ''
|
tailVouchUrl = ''
|
||||||
if ($http_host ~ "\.tail\.${networking.domain}$") {
|
if ($http_host ~ "\.tail\.${networking.domain}$") {
|
||||||
set $vouch_url $scheme://${config.vouch.tailDomain};
|
set $vouch_url $vouch_scheme://${config.vouch.tailDomain};
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
in mkMerge [
|
in mkMerge [
|
||||||
(mkBefore ''
|
(mkBefore ''
|
||||||
set $vouch_url ${config.vouch.url};
|
set $vouch_url ${config.vouch.url};
|
||||||
|
set $vouch_scheme $scheme;
|
||||||
'')
|
'')
|
||||||
|
(mkIf config.local.trusted (mkBefore ''
|
||||||
|
if ($http_x_forwarded_proto) {
|
||||||
|
set $vouch_scheme $http_x_forwarded_proto;
|
||||||
|
}
|
||||||
|
''))
|
||||||
(mkIf (config.local.enable or false) localVouchUrl)
|
(mkIf (config.local.enable or false) localVouchUrl)
|
||||||
(mkIf (config.local.enable or false && tailscale.enable) tailVouchUrl)
|
(mkIf (config.local.enable or false && tailscale.enable) tailVouchUrl)
|
||||||
''
|
''
|
||||||
return 302 $vouch_url/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
|
return 302 $vouch_url/login?url=$vouch_scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
|
||||||
''
|
''
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
"/validate" = {
|
"/validate" = {
|
||||||
recommendedProxySettings = false;
|
recommendedProxySettings = false;
|
||||||
proxyPass = "${config.vouch.proxyOrigin}/validate";
|
proxyPass = "${config.vouch.proxyOrigin}/validate";
|
||||||
extraConfig = ''
|
extraConfig = mkMerge [
|
||||||
proxy_set_header Host $host;
|
(mkIf (!config.vouch.doubleProxy) ''
|
||||||
proxy_pass_request_body off;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Content-Length "";
|
'')
|
||||||
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
|
(mkIf config.vouch.doubleProxy ''
|
||||||
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
|
proxy_set_header X-Host $host;
|
||||||
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
|
'')
|
||||||
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
|
''
|
||||||
'';
|
proxy_pass_request_body off;
|
||||||
|
proxy_set_header Content-Length "";
|
||||||
|
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
|
||||||
|
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
|
||||||
|
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
|
||||||
|
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
|
||||||
|
''
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
@ -7,10 +7,11 @@
|
||||||
let
|
let
|
||||||
inherit (lib.options) mkOption;
|
inherit (lib.options) mkOption;
|
||||||
inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault;
|
inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault;
|
||||||
inherit (config.services) tailscale;
|
inherit (config) networking;
|
||||||
inherit (config.services.nginx) virtualHosts;
|
inherit (config.services) tailscale nginx;
|
||||||
|
inherit (nginx) virtualHosts;
|
||||||
cfg = config.services.kanidm;
|
cfg = config.services.kanidm;
|
||||||
access = config.services.nginx.access.kanidm;
|
access = nginx.access.kanidm;
|
||||||
proxyPass = mkDefault "https://${access.host}:${toString access.port}";
|
proxyPass = mkDefault "https://${access.host}:${toString access.port}";
|
||||||
locations = {
|
locations = {
|
||||||
"/" = {
|
"/" = {
|
||||||
|
|
@ -20,6 +21,11 @@ let
|
||||||
alias = "${cfg.server.unencrypted.package.ca}";
|
alias = "${cfg.server.unencrypted.package.ca}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
localLocations = vouchDomain: {
|
||||||
|
"/".extraConfig = ''
|
||||||
|
proxy_redirect $scheme://${nginx.access.vouch.domain or "login.${networking.domain}"}/ $scheme://${vouchDomain}/;
|
||||||
|
'';
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
imports = let
|
imports = let
|
||||||
inherit (meta) nixos;
|
inherit (meta) nixos;
|
||||||
|
|
@ -33,15 +39,15 @@ in {
|
||||||
};
|
};
|
||||||
domain = mkOption {
|
domain = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
default = "id.${config.networking.domain}";
|
default = "id.${networking.domain}";
|
||||||
};
|
};
|
||||||
localDomain = mkOption {
|
localDomain = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
default = "id.local.${config.networking.domain}";
|
default = "id.local.${networking.domain}";
|
||||||
};
|
};
|
||||||
tailDomain = mkOption {
|
tailDomain = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
default = "id.tail.${config.networking.domain}";
|
default = "id.tail.${networking.domain}";
|
||||||
};
|
};
|
||||||
port = mkOption {
|
port = mkOption {
|
||||||
type = port;
|
type = port;
|
||||||
|
|
@ -85,13 +91,19 @@ in {
|
||||||
inherit (virtualHosts.${access.domain}) useACMEHost;
|
inherit (virtualHosts.${access.domain}) useACMEHost;
|
||||||
addSSL = mkDefault (access.useACMEHost != null || virtualHosts.${access.domain}.forceSSL);
|
addSSL = mkDefault (access.useACMEHost != null || virtualHosts.${access.domain}.forceSSL);
|
||||||
local.enable = true;
|
local.enable = true;
|
||||||
inherit locations;
|
locations = mkMerge [
|
||||||
|
locations
|
||||||
|
(localLocations nginx.access.vouch.localDomain or "login.local.${networking.domain}")
|
||||||
|
];
|
||||||
};
|
};
|
||||||
${access.tailDomain} = mkIf tailscale.enable {
|
${access.tailDomain} = mkIf tailscale.enable {
|
||||||
inherit (virtualHosts.${access.domain}) useACMEHost;
|
inherit (virtualHosts.${access.domain}) useACMEHost;
|
||||||
addSSL = mkDefault (access.useACMEHost != null || virtualHosts.${access.domain}.forceSSL);
|
addSSL = mkDefault (access.useACMEHost != null || virtualHosts.${access.domain}.forceSSL);
|
||||||
local.enable = true;
|
local.enable = true;
|
||||||
inherit locations;
|
locations = mkMerge [
|
||||||
|
locations
|
||||||
|
(localLocations nginx.access.vouch.tailDomain or "login.tail.${networking.domain}")
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit (lib.options) mkOption;
|
inherit (lib.options) mkOption;
|
||||||
inherit (lib.modules) mkIf mkDefault mkOptionDefault;
|
inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault;
|
||||||
inherit (config.services) tailscale;
|
inherit (config) networking;
|
||||||
|
inherit (config.services) tailscale nginx;
|
||||||
cfg = config.services.vouch-proxy;
|
cfg = config.services.vouch-proxy;
|
||||||
access = config.services.nginx.access.vouch;
|
access = nginx.access.vouch;
|
||||||
in {
|
in {
|
||||||
options.services.nginx.access.vouch = with lib.types; {
|
options.services.nginx.access.vouch = with lib.types; {
|
||||||
url = mkOption {
|
url = mkOption {
|
||||||
|
|
@ -15,15 +16,15 @@ in {
|
||||||
};
|
};
|
||||||
domain = mkOption {
|
domain = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
default = "login.${config.networking.domain}";
|
default = "login.${networking.domain}";
|
||||||
};
|
};
|
||||||
localDomain = mkOption {
|
localDomain = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
default = "login.local.${config.networking.domain}";
|
default = "login.local.${networking.domain}";
|
||||||
};
|
};
|
||||||
tailDomain = mkOption {
|
tailDomain = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
default = "login.tail.${config.networking.domain}";
|
default = "login.tail.${networking.domain}";
|
||||||
};
|
};
|
||||||
useACMEHost = mkOption {
|
useACMEHost = mkOption {
|
||||||
type = nullOr str;
|
type = nullOr str;
|
||||||
|
|
@ -38,21 +39,47 @@ in {
|
||||||
in mkOptionDefault "http://${host}:${toString cfg.port}";
|
in mkOptionDefault "http://${host}:${toString cfg.port}";
|
||||||
};
|
};
|
||||||
virtualHosts = let
|
virtualHosts = let
|
||||||
location = {
|
locations = {
|
||||||
proxy.websocket.enable = true;
|
"/" = {
|
||||||
proxyPass = access.url;
|
proxyPass = mkDefault access.url;
|
||||||
recommendedProxySettings = false;
|
extraConfig = ''
|
||||||
|
proxy_redirect default;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/validate" = { config, ... }: {
|
||||||
|
proxyPass = mkDefault (access.url + "/validate");
|
||||||
|
recommendedProxySettings = mkDefault false;
|
||||||
|
extraConfig = if config.local.trusted then ''
|
||||||
|
if ($http_x_host = ''') {
|
||||||
|
set $http_x_host $host;
|
||||||
|
}
|
||||||
|
proxy_set_header Host $http_x_host;
|
||||||
|
'' else ''
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
localLocations = kanidmDomain: {
|
||||||
|
"/".extraConfig = ''
|
||||||
|
proxy_redirect $scheme://${nginx.access.kanidm.domain or "id.${networking.domain}"}/ $scheme://${kanidmDomain}/;
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
${access.localDomain} = mkIf (access.useACMEHost != null) {
|
${access.localDomain} = mkIf (access.useACMEHost != null) {
|
||||||
local.enable = true;
|
local.enable = true;
|
||||||
locations."/" = location;
|
locations = mkMerge [
|
||||||
|
locations
|
||||||
|
(localLocations nginx.access.kanidm.localDomain or "id.local.${networking.domain}")
|
||||||
|
];
|
||||||
useACMEHost = mkDefault access.useACMEHost;
|
useACMEHost = mkDefault access.useACMEHost;
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
};
|
};
|
||||||
${access.tailDomain} = mkIf tailscale.enable {
|
${access.tailDomain} = mkIf tailscale.enable {
|
||||||
local.enable = true;
|
local.enable = true;
|
||||||
locations."/" = location;
|
locations = mkMerge [
|
||||||
|
locations
|
||||||
|
(localLocations nginx.access.kanidm.tailDomain or "id.tail.${networking.domain}")
|
||||||
|
];
|
||||||
useACMEHost = mkDefault access.useACMEHost;
|
useACMEHost = mkDefault access.useACMEHost;
|
||||||
addSSL = mkIf (access.useACMEHost != null) (mkDefault true);
|
addSSL = mkIf (access.useACMEHost != null) (mkDefault true);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,6 @@ in {
|
||||||
credentialsFile = config.sops.secrets.cloudflared-tunnel-apartment.path;
|
credentialsFile = config.sops.secrets.cloudflared-tunnel-apartment.path;
|
||||||
default = "http_status:404";
|
default = "http_status:404";
|
||||||
ingress = listToAttrs [
|
ingress = listToAttrs [
|
||||||
(ingressForNginx {
|
|
||||||
host = config.networking.domain;
|
|
||||||
inherit hostName;
|
|
||||||
})
|
|
||||||
(ingressForNginx {
|
(ingressForNginx {
|
||||||
host = config.services.zigbee2mqtt.domain;
|
host = config.services.zigbee2mqtt.domain;
|
||||||
inherit hostName;
|
inherit hostName;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,16 @@ in {
|
||||||
./cloudflared.nix
|
./cloudflared.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
services.nginx = let
|
||||||
|
inherit (config.services.nginx) access;
|
||||||
|
in {
|
||||||
|
virtualHosts = {
|
||||||
|
${access.zigbee2mqtt.domain} = {
|
||||||
|
local.denyGlobal = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
sops.defaultSopsFile = ./secrets.yaml;
|
sops.defaultSopsFile = ./secrets.yaml;
|
||||||
|
|
||||||
networking.firewall = {
|
networking.firewall = {
|
||||||
|
|
|
||||||
1
tree.nix
1
tree.nix
|
|
@ -56,6 +56,7 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"modules/nixos/network".functor.enable = true;
|
"modules/nixos/network".functor.enable = true;
|
||||||
|
"modules/nixos/nginx".functor.enable = true;
|
||||||
"modules/nixos/steam".functor.enable = true;
|
"modules/nixos/steam".functor.enable = true;
|
||||||
"modules/meta".functor.enable = true;
|
"modules/meta".functor.enable = true;
|
||||||
"modules/system".functor.enable = true;
|
"modules/system".functor.enable = true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue