Merge branch 'dev' into staging

This commit is contained in:
florian 2024-03-17 13:26:36 +01:00
commit 95f4946fbe
No known key found for this signature in database
GPG key ID: 93EE47CC3D061500
17 changed files with 203 additions and 76 deletions

View file

@ -47,8 +47,8 @@ jobs:
- name: Checkout source code
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
- name: Replace VERSION
if: inputs.RELEASE == 'testing'
run: ./misc/update-version.sh testing
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev'
run: ./misc/update-version.sh ${{ inputs.RELEASE }}
- name: Setup SSH for ARM node
if: inputs.CACHE_SUFFIX == 'arm'
run: |

View file

@ -976,15 +976,21 @@ To simplify the installation process, Linux package repositories for BunkerWeb a
sudo apt install -y nginx=1.24.0-1~$(lsb_release -cs)
```
!!! warning "Testing version"
If you use the `testing` version, you will need to add the `force-bad-version` directive to your `/etc/dpkg/dpkg.cfg` file before installing BunkerWeb.
!!! warning "Testing/dev version"
If you use the `testing` or `dev` version, you will need to add the `force-bad-version` directive to your `/etc/dpkg/dpkg.cfg` file before installing BunkerWeb.
Optional step : if you want to automatically enable the [setup wizard](web-ui.md#setup-wizard) when BunkerWeb is installed, export the following variable :
```shell
export UI_WIZARD=1
```
And finally install BunkerWeb 1.5.6 :
```shell
curl -s https://packagecloud.io/install/repositories/bunkerity/bunkerweb/script.deb.sh | sudo bash && \
sudo apt update && \
sudo apt install -y bunkerweb=1.5.6
sudo -E apt install -y bunkerweb=1.5.6
```
To prevent upgrading NGINX and/or BunkerWeb packages when executing `apt upgrade`, you can use the following command :
@ -1013,15 +1019,21 @@ To simplify the installation process, Linux package repositories for BunkerWeb a
sudo apt install -y nginx=1.24.0-1~jammy
```
!!! warning "Testing version"
If you use the `testing` version, you will need to add the `force-bad-version` directive to your `/etc/dpkg/dpkg.cfg` file before installing BunkerWeb.
!!! warning "Testing/dev version"
If you use the `testing` or `dev` version, you will need to add the `force-bad-version` directive to your `/etc/dpkg/dpkg.cfg` file before installing BunkerWeb.
Optional step : if you want to automatically enable the [setup wizard](web-ui.md#setup-wizard) when BunkerWeb is installed, export the following variable :
```shell
export UI_WIZARD=1
```
And finally install BunkerWeb 1.5.6 :
```shell
curl -s https://packagecloud.io/install/repositories/bunkerity/bunkerweb/script.deb.sh | sudo bash && \
sudo apt update && \
sudo apt install -y bunkerweb=1.5.6
sudo -E apt install -y bunkerweb=1.5.6
```
To prevent upgrading NGINX and/or BunkerWeb packages when executing `apt upgrade`, you can use the following command :
@ -1038,6 +1050,12 @@ To simplify the installation process, Linux package repositories for BunkerWeb a
sudo dnf install -y nginx-1.24.0
```
Optional step : if you want to automatically enable the [setup wizard](web-ui.md#setup-wizard) when BunkerWeb is installed, export the following variable :
```shell
export UI_WIZARD=1
```
And finally install BunkerWeb 1.5.6 :
```shell
@ -1045,7 +1063,7 @@ To simplify the installation process, Linux package repositories for BunkerWeb a
sed 's/yum install -y pygpgme --disablerepo='\''bunkerity_bunkerweb'\''/yum install -y python-gnupg/g' | \
sed 's/pypgpme_check=`rpm -qa | grep -qw pygpgme`/python-gnupg_check=`rpm -qa | grep -qw python-gnupg`/g' | sudo bash && \
sudo dnf makecache && \
sudo dnf install -y bunkerweb-1.5.6
sudo -E dnf install -y bunkerweb-1.5.6
```
To prevent upgrading NGINX and/or BunkerWeb packages when executing `dnf upgrade`, you can use the following command :
@ -1082,13 +1100,20 @@ To simplify the installation process, Linux package repositories for BunkerWeb a
```shell
sudo dnf install nginx-1.24.0
```
Optional step : if you want to automatically enable the [setup wizard](web-ui.md#setup-wizard) when BunkerWeb is installed, export the following variable :
```shell
export UI_WIZARD=1
```
And finally install BunkerWeb 1.5.6 :
```shell
dnf install -y epel-release && \
curl -s https://packagecloud.io/install/repositories/bunkerity/bunkerweb/script.rpm.sh | sudo bash && \
sudo dnf check-update && \
sudo dnf install -y bunkerweb-1.5.6
sudo -E dnf install -y bunkerweb-1.5.6
```
To prevent upgrading NGINX and/or BunkerWeb packages when executing `dnf upgrade`, you can use the following command :

View file

@ -410,7 +410,7 @@ Review your final BunkerWeb UI URL and then click on the `Setup` button. Once th
name: cr-bunkerweb
rules:
- apiGroups: [""]
resources: ["services", "pods", "configmaps"]
resources: ["services", "pods", "configmaps", "secrets"]
verbs: ["get", "watch", "list"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
@ -713,44 +713,12 @@ Review your final BunkerWeb UI URL and then click on the `Setup` button. Once th
=== "Linux"
If you want to use the setup wizard, you will need to set the `UI_HOST` setting to the HTTP endpoint of your web UI SERVICE. Since the web UI is listening on the same machine as BunkerWeb, you will need to set the `UI_HOST` setting `http://127.0.0.1:7000`.
If you want to use the setup wizard, you will need to run the following command `export UI_WIZARD=1` before installing BunkerWeb as described in [integrations section](integrations.md#linux) of the documentation (this is an optional step).
!!! tip "Accessing the setup wizard"
You can access the setup wizard by browsing the `http://your-ip-address/setup` URI of your server.
Here is the `/etc/bunkerweb/variables.env` boilerplate you can use :
```conf
HTTP_PORT=80
HTTPS_PORT=443
DNS_RESOLVERS=9.9.9.9 8.8.8.8 8.8.4.4
API_LISTEN_IP=127.0.0.1
SERVER_NAME=
MULTISITE=yes
UI_HOST=http://127.0.0.1:7000
```
The web UI comes as systemd service named `bunkerweb-ui` which is not enabled by default. If you want to start the web UI when on startup you can run the following command :
```shell
systemctl enable bunkerweb-ui
```
Create an empty configuration file for the web UI :
```shell
echo "" > /etc/bunkerweb/ui.env
chown nginx:nginx /etc/bunkerweb/ui.env
```
Don't forget to restart the `bunkerweb` and `bunkerweb-ui` services :
```shell
systemctl restart bunkerweb
systemctl restart bunkerweb-ui
```
## Account management
You can change the username, password needed and manage two-factor authentication by **accessing the account page** of the web UI from the menu.
@ -1253,7 +1221,7 @@ After a successful login/password combination, you will be prompted to enter you
name: cr-bunkerweb
rules:
- apiGroups: [""]
resources: ["services", "pods", "configmaps"]
resources: ["services", "pods", "configmaps", "secrets"]
verbs: ["get", "watch", "list"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]

View file

@ -115,6 +115,7 @@ access_by_lua_block {
end
logger:log(NOTICE, plugin_id .. " redirect to " .. ret.redirect .. " : " .. ret.msg)
redirect = ret.redirect
ctx.bw.location_header = redirect
break
end
end

View file

@ -19,6 +19,14 @@ function errors:initialize(ctx)
plugin.initialize(self, "errors", ctx)
-- Default error texts
self.default_errors = {
["301"] = {
title = "Moved Permanently",
text = "The requested page has moved to a new url.",
},
["302"] = {
title = "Found",
text = "The requested page has moved temporarily to a new url.",
},
["400"] = {
title = "Bad Request",
text = "The server did not understand the request.",

View file

@ -16,7 +16,7 @@
},
"INTERCEPTED_ERROR_CODES": {
"context": "multisite",
"default": "400 401 403 404 405 413 429 500 501 502 503 504",
"default": "301 302 400 401 403 404 405 413 429 500 501 502 503 504",
"help": "List of HTTP error code intercepted by BunkerWeb",
"id": "intercepted-error-codes",
"label": "Intercepted error codes",

View file

@ -46,6 +46,15 @@ function misc:access()
return self:ret(true, "method " .. method .. " is not allowed", HTTP_NOT_ALLOWED)
end
function misc:header()
-- Add Location header if needed
if self.ctx.bw.location_header then
ngx.header["Location"] = self.ctx.bw.location_header
return self:ret(true, "edited location header")
end
return self:ret(true, "no location header needed")
end
function misc:log_default()
if self.variables["DISABLE_DEFAULT_SERVER"] == "yes" then
self:set_metric("counters", "failed_default", 1)

View file

@ -31,7 +31,14 @@
"cors",
"antibot"
],
"headers": ["headers", "cors", "reverseproxy", "clientcache", "antibot"],
"headers": [
"headers",
"cors",
"reverseproxy",
"clientcache",
"antibot",
"misc"
],
"log": ["badbehavior", "bunkernet", "errors", "metrics"],
"preread": [
"whitelist",

View file

@ -88,10 +88,9 @@ app.config["SECRET_KEY"] = getenv("FLASK_SECRET", urandom(32))
PROXY_NUMBERS = int(getenv("PROXY_NUMBERS", "1"))
app.wsgi_app = ReverseProxied(app.wsgi_app, x_for=PROXY_NUMBERS, x_proto=PROXY_NUMBERS, x_host=PROXY_NUMBERS, x_prefix=PROXY_NUMBERS)
gunicorn_logger = getLogger("gunicorn.error")
app.logger.handlers = gunicorn_logger.handlers
app.logger = gunicorn_logger
app.logger.setLevel(gunicorn_logger.level)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"
@ -669,6 +668,51 @@ def account():
# Check form data validity
is_request_form("account")
if request.form["operation"] not in ("username", "password", "totp", "activate-key"):
return redirect_flash_error("Invalid operation parameter.", "account")
if request.form["operation"] == "activate-key":
is_request_params(["license"], "account")
if len(request.form["license"]) == 0:
return redirect_flash_error("The license key is empty", "account")
variable = {}
variable["PRO_LICENSE_KEY"] = request.form["license"]
error = app.config["CONFIG"].check_variables(variable)
if error:
return redirect_flash_error("The license key variable checks returned error", "account", True)
# Force job to contact PRO API
# by setting the last check to None
metadata = db.get_metadata()
metadata["last_pro_check"] = None
db.set_pro_metadata(metadata)
# Reload instances
app.config["RELOADING"] = True
app.config["LAST_RELOAD"] = time()
Thread(
target=manage_bunkerweb,
name="Reloading instances",
args=(
"global_config",
variable,
),
).start()
flash("Checking license key to upgrade.", "success")
return redirect(
url_for(
"loading",
next=url_for("account"),
message="Saving license key",
)
)
is_request_params(["operation", "curr_password"], "account")
if not current_user.check_password(request.form["curr_password"]):
@ -679,9 +723,6 @@ def account():
is_two_factor_enabled = current_user.is_two_factor_enabled
secret_token = current_user.secret_token
if request.form["operation"] not in ("username", "password", "totp"):
return redirect_flash_error("Invalid operation parameter.", "account")
if request.form["operation"] == "username":
is_request_params(["admin_username"], "account")
@ -997,6 +1038,12 @@ def global_config():
),
).start()
try:
if config["PRO_LICENSE_KEY"]["value"] != variables["PRO_LICENSE_KEY"]:
flash("Checking license key to upgrade.", "success")
except:
pass
return redirect(
url_for(
"loading",

File diff suppressed because one or more lines are too long

View file

@ -5,9 +5,9 @@
<h5 class="my-2 font-bold dark:text-white/90 mx-2">SETTINGS</h5>
{% set tabs = [
{
"name": "Global",
"id": "global",
"description": "Global informations"
"name": "PRO",
"id": "pro",
"description": "BunkerWeb PRO"
},
{
"name": "Username",
@ -55,23 +55,23 @@
<!--end tabs-->
</div>
</div>
{% set global_info = {
{% set pro_info = {
"message" : "Pro version" if is_pro_version else "Pro version but exceeding services" if pro_status == "active" and pro_overlapped else "Pro version is expired" if pro_status == "expired" else "Pro version suspended" if pro_status == "suspended" else "You are using free version",
"link_message" : "All features available" if is_pro_version else "Awaiting compliance" if pro_status == "active" and pro_overlapped else "Renew license" if pro_status == "expired" else "Talk to team" if pro_status == "suspended" else "Upgrade to pro",
"icon" : "pro" if is_pro_version else "free"
} %}
<div data-tab-item="global"
<div data-tab-item="pro"
class="grid grid-cols-12 w-full justify-items-center">
<div class="col-span-12">
<h5 class="text-xl my-1 text-center transition duration-300 ease-in-out font-bold m-0 mb-4 dark:text-gray-200">
VERSION
PRO
</h5>
<div class="flex justify-center items-center">
<p class="mb-0 mr-2 dark:text-gray-300">{{ global_info['message'] }}</p>
<p class="mb-0 mr-2 dark:text-gray-300">{{ pro_info['message'] }}</p>
<div role="img"
aria-label="version"
aria-label="pro"
class="dark:brightness-90 inline-block w-8 h-8 text-center rounded-circle bg-yellow-500">
{% if global_info['icon'] == 'pro' %}
{% if pro_info['icon'] == 'pro' %}
<svg class="leading-none text-lg relative scale-[0.6]"
viewBox="0 0 48 46"
fill="none"
@ -91,12 +91,13 @@
{% endif %}
</div>
</div>
{% if global_info['link_message'] %}
<div class="flex justify-center mt-2">
<a class="text-center font-semibold text-yellow-500 underline"
href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro">{{ global_info['link_message'] }}</a>
</div>
{% if pro_info['link_message'] %}
<div class="flex justify-center mt-2">
<a class="text-center font-semibold text-yellow-500 underline"
href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro">{{ pro_info['link_message'] }}</a>
</div>
{% endif %}
{% if is_pro_version %}
<div class="mt-2 flex flex-col justify-center items-center">
{% if pro_expire %}
@ -115,6 +116,63 @@
</div>
{% endif %}
</div>
{% if not is_pro_version and pro_status != "inactive" %}
<form
class="mt-6 relative col-span-12 grid grid-cols-12 w-full justify-items-center"
id="activate-key-form"
action="account"
method="POST"
autocomplete="off">
<div class="col-span-12">
<h5 class="text-xl my-1 transition duration-300 ease-in-out text-md font-bold m-0 dark:text-gray-200">ACTIVATE KEY</h5>
</div>
<input type="hidden" name="operation" value="activate-key" />
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden"
name="next"
value="{{ request.values.get('next', '') }}" />
<div data-input-group
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">License key</h5>
<label class="sr-only" for="license">License key</label>
<input type="password"
id="license"
name="license"
class="col-span-12 regular-input"
placeholder="enter new license key"
value=""
pattern="^.*$"
required />
<div data-setting-password-container
class="absolute flex right-6 md:right-8 h-5 w-5 top-[55%] md:top-[53%]">
<button data-setting-password="visible"
class="h-5 w-5 flex items-center align-middle dark:fill-blue-500 hover:brightness-75 transition-all"
type="button">
<svg class="fill-primary pointer-events-none dark:fill-blue-500 hover:brightness-75 transition-all"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512">
<path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z" />
</svg>
</button>
<button data-setting-password="invisible"
class="hidden -translate-y-0.2 scale-110 h-5 w-5 items-center align-middle"
type="button">
<svg class="fill-primary pointer-events-none dark:fill-blue-500 hover:brightness-75 transition-all"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512">
<path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z" />
</svg>
</button>
</div>
</div>
<div class="col-span-12 flex justify-center mt-6">
<button type="submit"
id="activate-key-button"
name="activate-key-button"
class="valid-btn">SAVE</button>
</div>
</form>
{% endif %}
</div>
<div data-tab-item="username"
class="hidden grid grid-cols-12 w-full justify-items-center">

View file

@ -1,7 +1,7 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- main container -->
<div data-{{ current_endpoint }}-container class="max-h-[700px] flex flex-col justify-between :brightness-110 md:min-h-50-screen col-span-12 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div class="mb-4 px-3 max-h-[350px] md:max-h-[500px] overflow-y-auto overflow-x-hidden">
<div data-{{ current_endpoint }}-container class="min-h-[400px] flex flex-col justify-between dark:brightness-110 md:min-h-50-screen col-span-12 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div class="mb-4 px-3">
<div class="w-full grid-cols-12 grid">
<div class="col-span-12 md:col-span-8">
<h5 class="mb-2 font-bold dark:text-white/90">
@ -57,7 +57,7 @@
{% endif %}
<!-- end service root-->
<!-- services folder -->
{% if child['type'] == "folder" and current_endpoint == "configs" and loop.depth != 1 %}
{% if child['type'] == "folder" and current_endpoint == "configs" and loop.depth != 1 or child['type'] == "folder" and current_endpoint == "cache" and loop.depth > 2 %}
<svg class="col-span-2 h-[2.5rem] w-[2.5rem] fill-primary stroke-gray-100 dark:stroke-gray-400"
xmlns="http://www.w3.org/2000/svg"
fill="none"

View file

@ -28,7 +28,7 @@
}
] %}
<div data-global-config-filter
class="h-fit p-4 col-span-12 md:col-span-6 lg:col-span-5 xl:col-span-4 2xl:col-span-3 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
class="h-fit p-4 col-span-12 md:col-span-6 lg:col-span-5 2xl:col-span-4 3xl:col-span-3 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<h5 class="mb-2 font-bold dark:text-white/90">FILTER</h5>
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
{% for filter in filters %}

View file

@ -210,7 +210,7 @@
<li class="mt-0.5 w-full">
<a class="dark:hover:bg-primary/20 hover:bg-primary/5 hover:rounded-lg dark:text-gray-200 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition"
href="{{ request.url_root }}plugins/{{ plugin['id'] }}">
<div class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5">
<div class="mr-2 flex flex-wrap items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5">
<svg class="fill-gray-500 h-5 w-5 relative"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512">
@ -224,8 +224,8 @@
{% if plugin['page'] and plugin['type'] == "pro" %}
<li class="mt-0.5 w-full">
<a {% if not is_pro_version %}target="_blank" rel="noopener"{% endif %} class="dark:hover:bg-primary/20 hover:bg-primary/5 hover:rounded-lg dark:text-gray-200 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition" href="{% if not is_pro_version %}https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro{% else %}javascript:void(0){% endif %}"
<div class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5">
<svg class="h-5 w-5 dark:brightness-90"
<div class="mr-2 flex flex-wrap items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5">
<svg class="h-5 w-5 dark:brightness-90 relative"
viewBox="0 0 48 46"
fill="none"
xmlns="http://www.w3.org/2000/svg">

View file

@ -414,6 +414,10 @@
}
updateResume() {
this.servInp.value = this.servInp.value.replace('https://', '').replace('http://', '');
if (!this.urlInp.value.startsWith("/")) {
this.urlInp.value = "/" + this.urlInp.value;
}
this.resumeEl.textContent = `http${
this.sslCheck.getAttribute("data-checked") === "true" ? "s" : ""
}://${this.servInp.value}${this.urlInp.value}`;

View file

@ -19,7 +19,7 @@ try:
log_info("Try to click on all available tabs ...")
assert_button_click(DRIVER, "//button[@data-tab-handler='global']")
assert_button_click(DRIVER, "//button[@data-tab-handler='pro']")
assert_button_click(DRIVER, "//button[@data-tab-handler='username']")
assert_button_click(DRIVER, "//button[@data-tab-handler='password']")
assert_button_click(DRIVER, "//button[@data-tab-handler='totp']")

View file

@ -4,7 +4,7 @@ metadata:
name: cr-bunkerweb
rules:
- apiGroups: [""]
resources: ["services", "pods", "configmaps"]
resources: ["services", "pods", "configmaps", "secrets"]
verbs: ["get", "watch", "list"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]