mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-02-04 11:07:37 -08:00
Translated ['src/pentesting-cloud/kubernetes-security/kubernetes-pivotin
This commit is contained in:
@@ -22,6 +22,7 @@ after = ["links"]
|
||||
|
||||
[preprocessor.hacktricks]
|
||||
command = "python3 ./hacktricks-preprocessor.py"
|
||||
env = "prod"
|
||||
|
||||
[output.html]
|
||||
additional-css = ["theme/pagetoc.css", "theme/tabs.css"]
|
||||
|
||||
@@ -30,14 +30,16 @@ def ref(matchobj):
|
||||
href = matchobj.groups(0)[0].strip()
|
||||
title = href
|
||||
if href.startswith("http://") or href.startswith("https://"):
|
||||
# pass
|
||||
try:
|
||||
raw_html = str(urlopen(Request(href, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0'})).read())
|
||||
match = re.search('<title>(.*?)</title>', raw_html)
|
||||
title = match.group(1) if match else href
|
||||
except Exception as e:
|
||||
logger.debug(f'Error opening URL {href}: {e}')
|
||||
pass #nDont stop on broken link
|
||||
if context['config']['preprocessor']['hacktricks']['env'] == 'dev':
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
raw_html = str(urlopen(Request(href, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0'})).read())
|
||||
match = re.search('<title>(.*?)</title>', raw_html)
|
||||
title = match.group(1) if match else href
|
||||
except Exception as e:
|
||||
logger.debug(f'Error opening URL {href}: {e}')
|
||||
pass #nDont stop on broken link
|
||||
else:
|
||||
try:
|
||||
if href.endswith("/"):
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- Ορίστε τη μεταβλητή περιβάλλοντος GOOGLE_APPLICATION_CREDENTIALS που δείχνει στο μονοπάτι όπου βρίσκεται το json.
|
||||
|
||||
> [!WARNING]
|
||||
> Επομένως, ως **επιτιθέμενος**, αν παραβιάσετε ένα κοντέινερ μέσα σε ένα pod, θα πρέπει να ελέγξετε για αυτή τη **μεταβλητή** **env** και τα **αρχεία** **json** με τα διαπιστευτήρια GCP.
|
||||
> Επομένως, ως **επιτιθέμενος**, αν παραβιάσετε ένα κοντέινερ μέσα σε ένα pod, θα πρέπει να ελέγξετε αυτή τη **μεταβλητή** **env** και τα **αρχεία** **json** με τα διαπιστευτήρια GCP.
|
||||
|
||||
### Relating GSA json to KSA secret
|
||||
|
||||
@@ -59,7 +59,7 @@ gcloud container clusters update <cluster_name> \
|
||||
# You could update instead of create
|
||||
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
|
||||
```
|
||||
- Δημιουργήστε το **GCP Service Account για να προσποιηθείτε** από το K8s με δικαιώματα GCP:
|
||||
- Δημιουργήστε τον **GCP Service Account για να προσποιηθείτε** από το K8s με δικαιώματα GCP:
|
||||
```bash
|
||||
# Create SA called "gsa2ksa"
|
||||
gcloud iam service-accounts create gsa2ksa --project=<project-id>
|
||||
@@ -80,7 +80,7 @@ kubectl create namespace testing
|
||||
# Create the KSA
|
||||
kubectl create serviceaccount ksa2gcp -n testing
|
||||
```
|
||||
- **Δέστε το GSA με το KSA**
|
||||
- **Σύνδεση του GSA με το KSA**
|
||||
```bash
|
||||
# Allow the KSA to access the GSA in GCP IAM
|
||||
gcloud iam service-accounts add-iam-policy-binding gsa2ksa@<project-id.iam.gserviceaccount.com \
|
||||
@@ -126,7 +126,7 @@ gcloud auth activate-service-account --key-file=/var/run/secrets/google/service-
|
||||
> Ως επιτιθέμενος μέσα στο K8s θα πρέπει να **αναζητήσετε SAs** με την **`iam.gke.io/gcp-service-account` αναφορά** καθώς αυτό υποδεικνύει ότι ο SA μπορεί να έχει πρόσβαση σε κάτι στο GCP. Μια άλλη επιλογή θα ήταν να προσπαθήσετε να εκμεταλλευτείτε κάθε KSA στο cluster και να ελέγξετε αν έχει πρόσβαση.\
|
||||
> Από το GCP είναι πάντα ενδιαφέρον να απαριθμήσετε τους δεσμούς και να γνωρίζετε **ποια πρόσβαση δίνετε σε SAs μέσα στο Kubernetes**.
|
||||
|
||||
Αυτό είναι ένα σενάριο για να **επικαλύψετε εύκολα όλες τις ορισμούς pods** **αναζητώντας** αυτή την **αναφορά**:
|
||||
Αυτό είναι ένα σενάριο για να **επικοινωνήσετε εύκολα με όλους τους ορισμούς pods** **αναζητώντας** αυτή την **αναφορά**:
|
||||
```bash
|
||||
for ns in `kubectl get namespaces -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
|
||||
for pod in `kubectl get pods -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
|
||||
@@ -141,7 +141,7 @@ done | grep -B 1 "gcp-service-account"
|
||||
|
||||
### Kiam & Kube2IAM (IAM ρόλος για Pods) <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>
|
||||
|
||||
Μια (παρωχημένη) μέθοδος για να δώσετε IAM Ρόλους σε Pods είναι να χρησιμοποιήσετε έναν [**Kiam**](https://github.com/uswitch/kiam) ή έναν [**Kube2IAM**](https://github.com/jtblin/kube2iam) **server.** Βασικά, θα χρειαστεί να εκτελέσετε ένα **daemonset** στο cluster σας με έναν **τύπο προνομιακού IAM ρόλου**. Αυτό το daemonset θα είναι αυτό που θα δώσει πρόσβαση σε IAM ρόλους στα pods που το χρειάζονται.
|
||||
Μια (παρωχημένη) μέθοδος για να δώσετε IAM Roles σε Pods είναι να χρησιμοποιήσετε έναν [**Kiam**](https://github.com/uswitch/kiam) ή έναν [**Kube2IAM**](https://github.com/jtblin/kube2iam) **server.** Βασικά, θα χρειαστεί να εκτελέσετε ένα **daemonset** στο cluster σας με έναν **τύπο προνομιακού IAM ρόλου**. Αυτό το daemonset θα είναι αυτό που θα δώσει πρόσβαση σε IAM ρόλους στα pods που το χρειάζονται.
|
||||
|
||||
Πρώτα απ' όλα, πρέπει να ρυθμίσετε **ποιοι ρόλοι μπορούν να προσπελαστούν μέσα στο namespace**, και το κάνετε με μια αναφορά μέσα στο αντικείμενο namespace:
|
||||
```yaml:Kiam
|
||||
@@ -192,14 +192,14 @@ image: alpine
|
||||
command: ["/bin/sh"]
|
||||
args: ["-c", "sleep 100000"]' | kubectl apply -f -
|
||||
```
|
||||
### IAM Role for K8s Service Accounts via OIDC <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>
|
||||
### IAM Ρόλος για Λογαριασμούς Υπηρεσιών K8s μέσω OIDC <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>
|
||||
|
||||
Αυτή είναι η **συνιστώμενη μέθοδος από την AWS**.
|
||||
|
||||
1. Πρώτα απ' όλα πρέπει να [δημιουργήσετε έναν πάροχο OIDC για το cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html).
|
||||
2. Στη συνέχεια, δημιουργείτε έναν ρόλο IAM με τις άδειες που θα απαιτεί η SA.
|
||||
3. Δημιουργήστε μια [σχέση εμπιστοσύνης μεταξύ του ρόλου IAM και της SA](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) ονόματι (ή τα namespaces που δίνουν πρόσβαση στον ρόλο σε όλες τις SAs του namespace). _Η σχέση εμπιστοσύνης θα ελέγξει κυρίως το όνομα του παρόχου OIDC, το όνομα του namespace και το όνομα της SA_.
|
||||
4. Τέλος, **δημιουργήστε μια SA με μια αναφορά που υποδεικνύει το ARN του ρόλου**, και τα pods που εκτελούνται με αυτή τη SA θα έχουν **πρόσβαση στο token του ρόλου**. Το **token** είναι **γραμμένο** μέσα σε ένα αρχείο και η διαδρομή καθορίζεται στο **`AWS_WEB_IDENTITY_TOKEN_FILE`** (προεπιλογή: `/var/run/secrets/eks.amazonaws.com/serviceaccount/token`)
|
||||
1. Πρώτα απ' όλα, πρέπει να [δημιουργήσετε έναν πάροχο OIDC για το cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html).
|
||||
2. Στη συνέχεια, δημιουργείτε έναν IAM ρόλο με τις άδειες που θα απαιτεί ο SA.
|
||||
3. Δημιουργήστε μια [σχέση εμπιστοσύνης μεταξύ του IAM ρόλου και του SA](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) ονόματος (ή των namespaces που δίνουν πρόσβαση στον ρόλο σε όλους τους SAs του namespace). _Η σχέση εμπιστοσύνης θα ελέγξει κυρίως το όνομα του παρόχου OIDC, το όνομα του namespace και το όνομα του SA_.
|
||||
4. Τέλος, **δημιουργήστε έναν SA με μια αναφορά που υποδεικνύει το ARN του ρόλου**, και τα pods που εκτελούνται με αυτόν τον SA θα έχουν **πρόσβαση στο token του ρόλου**. Το **token** είναι **γραμμένο** μέσα σε ένα αρχείο και η διαδρομή καθορίζεται στο **`AWS_WEB_IDENTITY_TOKEN_FILE`** (προεπιλογή: `/var/run/secrets/eks.amazonaws.com/serviceaccount/token`)
|
||||
```bash
|
||||
# Create a service account with a role
|
||||
cat >my-service-account.yaml <<EOF
|
||||
@@ -257,15 +257,15 @@ done | grep -B 1 "amazonaws.com"
|
||||
|
||||
Η προηγούμενη ενότητα αφορούσε το πώς να κλέψετε IAM Roles με pods, αλλά σημειώστε ότι ένα **Node του** K8s cluster θα είναι μια **περίπτωση μέσα στο cloud**. Αυτό σημαίνει ότι το Node είναι πολύ πιθανό να **έχει έναν νέο IAM ρόλο που μπορείτε να κλέψετε** (_σημειώστε ότι συνήθως όλα τα nodes ενός K8s cluster θα έχουν τον ίδιο IAM ρόλο, οπότε μπορεί να μην αξίζει να προσπαθήσετε να ελέγξετε κάθε node_).
|
||||
|
||||
Υπάρχει ωστόσο μια σημαντική απαίτηση για να έχετε πρόσβαση στο metadata endpoint από το node, πρέπει να είστε στο node (ssh session?) ή τουλάχιστον να έχετε το ίδιο δίκτυο:
|
||||
Υπάρχει ωστόσο μια σημαντική απαίτηση για να αποκτήσετε πρόσβαση στο metadata endpoint από το node, πρέπει να είστε στο node (ssh session;) ή τουλάχιστον να έχετε το ίδιο δίκτυο:
|
||||
```bash
|
||||
kubectl run NodeIAMStealer --restart=Never -ti --rm --image lol --overrides '{"spec":{"hostNetwork": true, "containers":[{"name":"1","image":"alpine","stdin": true,"tty":true,"imagePullPolicy":"IfNotPresent"}]}}'
|
||||
```
|
||||
### Κλέψε το Token Ρόλου IAM
|
||||
### Κλέψε το Token IAM Ρόλου
|
||||
|
||||
Προηγουμένως έχουμε συζητήσει πώς να **συνδέσουμε Ρόλους IAM σε Pods** ή ακόμα και πώς να **διαφύγουμε στον Κόμβο για να κλέψουμε τον Ρόλο IAM** που έχει συσχετιστεί με την παρουσία.
|
||||
Προηγουμένως έχουμε συζητήσει πώς να **συνδέσουμε IAM Ρόλους σε Pods** ή ακόμα και πώς να **διαφύγουμε στον Κόμβο για να κλέψουμε τον IAM Ρόλο** που έχει συσχετιστεί με την παρουσία.
|
||||
|
||||
Μπορείτε να χρησιμοποιήσετε το παρακάτω σενάριο για να **κλέψετε** τα νέα σκληρά κερδισμένα **διαπιστευτήρια ρόλου IAM**:
|
||||
Μπορείτε να χρησιμοποιήσετε το παρακάτω σενάριο για να **κλέψετε** τα νέα σκληρά κερδισμένα **διαπιστευτήρια IAM ρόλου** σας:
|
||||
```bash
|
||||
IAM_ROLE_NAME=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null || wget http://169.254.169.254/latest/meta-data/iam/security-credentials/ -O - 2>/dev/null)
|
||||
if [ "$IAM_ROLE_NAME" ]; then
|
||||
|
||||
@@ -590,6 +590,62 @@ function playground_text(playground, hidden = true) {
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
|
||||
(function menubarLanguage() {
|
||||
var menubarLanguageToggleButton = document.getElementById('menubar-languages-toggle');
|
||||
var menubarLanguagePopup = document.getElementById('menubar-languages-popup');
|
||||
var languageButtons = menubarLanguagePopup.querySelectorAll('.menu-bar-link');
|
||||
|
||||
function showLanguage() {
|
||||
menubarLanguagePopup.style.display = 'flex';
|
||||
menubarLanguageToggleButton.setAttribute('aria-expanded', true);
|
||||
}
|
||||
|
||||
function hideLanguage() {
|
||||
menubarLanguagePopup.style.display = 'none';
|
||||
menubarLanguageToggleButton.setAttribute('aria-expanded', false);
|
||||
menubarLanguageToggleButton.focus();
|
||||
}
|
||||
|
||||
menubarLanguageToggleButton.addEventListener('click', function () {
|
||||
if (menubarLanguagePopup.style.display === 'flex') {
|
||||
hideLanguage();
|
||||
} else {
|
||||
showLanguage();
|
||||
}
|
||||
});
|
||||
|
||||
menubarLanguagePopup.addEventListener('focusout', function(e) {
|
||||
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
||||
if (!!e.relatedTarget && !menubarLanguageToggleButton.contains(e.relatedTarget) && !menubarLanguagePopup.contains(e.relatedTarget)) {
|
||||
hideLanguage();
|
||||
}
|
||||
});
|
||||
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
||||
document.addEventListener('click', function(e) {
|
||||
if (menubarLanguagePopup.style.display === 'block' && !menubarLanguageToggleButton.contains(e.target) && !menubarLanguagePopup.contains(e.target)) {
|
||||
hideLanguage();
|
||||
}
|
||||
});
|
||||
|
||||
languageButtons.forEach((btn) => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
const regex = /(?:(?:\/)+(?<lang>[a-z]{2}(?=\/|$)))?(?<path>(?:\/)*.*)?/g
|
||||
var match = regex.exec(window.location.pathname)
|
||||
|
||||
var path = match.groups.path
|
||||
console.log(`Path: ${path} ${typeof path}`)
|
||||
|
||||
const lang = match.groups.lang
|
||||
console.log(`Lang: ${lang}`)
|
||||
|
||||
window.location = `/${e.target.id}${path}${window.location.hash}`
|
||||
});
|
||||
})
|
||||
})();
|
||||
|
||||
(function chapterNavigation() {
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
|
||||
@@ -83,6 +83,13 @@ body.sidebar-visible #menu-bar {
|
||||
}
|
||||
}
|
||||
|
||||
.right-buttons .icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
border: none;
|
||||
background: var(--bg);
|
||||
@@ -138,11 +145,13 @@ body.sidebar-visible #menu-bar {
|
||||
}
|
||||
|
||||
/* Collapse Menu Popup */
|
||||
|
||||
#menubar-collapse-toggle {
|
||||
position: relative;
|
||||
}
|
||||
#menubar-collapse-popup {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: var(--menu-bar-height);
|
||||
right: 0px;
|
||||
top: 35px;
|
||||
z-index: 105;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
@@ -172,6 +181,44 @@ body.sidebar-visible #menu-bar {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
/* Languages Menu Popup */
|
||||
#menubar-languages-toggle {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#menubar-languages-popup {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 35px;
|
||||
z-index: 105;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--table-border-color);
|
||||
margin: 0;
|
||||
padding: 0px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
max-height: 300px;
|
||||
width: 150px;
|
||||
overflow: scroll;
|
||||
}
|
||||
#menubar-languages-popup .menu-bar-link {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 8px 20px;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
#menubar-languages-popup .menu-bar-link:hover {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
display: flex;
|
||||
|
||||
@@ -144,34 +144,60 @@
|
||||
|
||||
<div class="right-buttons">
|
||||
<div id="menubar-collapse">
|
||||
<a class="menu-bar-link" href="https://training.hacktricks.xyz" target="_blank">
|
||||
Hacktricks Training
|
||||
</a>
|
||||
<a class="menu-bar-link" href="https://twitter.com/hacktricks_live" target="_blank">
|
||||
Twitter
|
||||
</a>
|
||||
<a class="menu-bar-link" href="https://www.linkedin.com/company/hacktricks" target="_blank">
|
||||
Linkedin
|
||||
</a>
|
||||
<a class="menu-bar-link" href="https://github.com/sponsors/carlospolop" target="_blank">
|
||||
Sponsor
|
||||
</a>
|
||||
</div>
|
||||
<button id="menubar-collapse-toggle" class="icon-button" type="button" title="Toggle menu bar" aria-label="Toggle Menu bar" aria-expanded="false" aria-controls="collapse">
|
||||
<i class="fa fa-ellipsis-h"></i>
|
||||
</button>
|
||||
<div id="menubar-collapse-popup" class="menubar-collapse-popup" aria-label="Menu" role="menu">
|
||||
<a href="https://training.hacktricks.xyz" target="_blank" role="menuitem" class="menu-bar-link">Hacktricks Training</a>
|
||||
<a href="https://twitter.com/hacktricks_live" target="_blank" role="menuitem" class="menu-bar-link">Twitter</a>
|
||||
<a href="https://www.linkedin.com/company/hacktricks" target="_blank" role="menuitem" class="menu-bar-link">Linkedin</a>
|
||||
<a href="https://github.com/sponsors/carlospolop" target="_blank" role="menuitem" class="menu-bar-link">Sponsor</a>
|
||||
<a class="menu-bar-link" href="https://training.hacktricks.xyz" target="_blank">
|
||||
Hacktricks Training
|
||||
</a>
|
||||
<a class="menu-bar-link" href="https://twitter.com/hacktricks_live" target="_blank">
|
||||
Twitter
|
||||
</a>
|
||||
<a class="menu-bar-link" href="https://www.linkedin.com/company/hacktricks" target="_blank">
|
||||
Linkedin
|
||||
</a>
|
||||
<a class="menu-bar-link" href="https://github.com/sponsors/carlospolop" target="_blank">
|
||||
Sponsor
|
||||
</a>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<div id="menubar-collapse-toggle" class="icon-button" type="button" title="Toggle menu bar" aria-label="Toggle Menu bar" aria-expanded="false" aria-controls="collapse">
|
||||
<i class="fa fa-ellipsis-h"></i>
|
||||
<div id="menubar-collapse-popup" class="menubar-collapse-popup" aria-label="Menu" role="menu">
|
||||
<a href="https://training.hacktricks.xyz" target="_blank" role="menuitem" class="menu-bar-link">Hacktricks Training</a>
|
||||
<a href="https://twitter.com/hacktricks_live" target="_blank" role="menuitem" class="menu-bar-link">Twitter</a>
|
||||
<a href="https://www.linkedin.com/company/hacktricks" target="_blank" role="menuitem" class="menu-bar-link">Linkedin</a>
|
||||
<a href="https://github.com/sponsors/carlospolop" target="_blank" role="menuitem" class="menu-bar-link">Sponsor</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if search_enabled}}
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{#if search_enabled}}
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
<div id="menubar-languages-toggle" class="icon-button" type="button" title="Translations" aria-label="Toggle Tanslations" aria-expanded="false" aria-controls="translations">
|
||||
<i class="fa fa-globe"></i>
|
||||
|
||||
<div id="menubar-languages-popup" class="menubar-languages-popup" aria-label="Language menu" role="language menu">
|
||||
<button id="en" role="menuitem" class="menu-bar-link">English</button>
|
||||
<button id="es" role="menuitem" class="menu-bar-link">Spanish</button>
|
||||
<button id="fr" role="menuitem" class="menu-bar-link">French</button>
|
||||
<button id="de" role="menuitem" class="menu-bar-link">German</button>
|
||||
<button id="el" role="menuitem" class="menu-bar-link">Greek</button>
|
||||
<button id="hi" role="menuitem" class="menu-bar-link">Hindi</button>
|
||||
<button id="it" role="menuitem" class="menu-bar-link">Italian</button>
|
||||
<button id="ja" role="menuitem" class="menu-bar-link">Japanese</button>
|
||||
<button id="ko" role="menuitem" class="menu-bar-link">Korean</button>
|
||||
<button id="pl" role="menuitem" class="menu-bar-link">Polish</button>
|
||||
<button id="pt" role="menuitem" class="menu-bar-link">Portuguese</button>
|
||||
<button id="sr" role="menuitem" class="menu-bar-link">Serbian</button>
|
||||
<button id="sw" role="menuitem" class="menu-bar-link">Swahili</button>
|
||||
<button id="tr" role="menuitem" class="menu-bar-link">Turkish</button>
|
||||
<button id="uk" role="menuitem" class="menu-bar-link">Ukrainian</button>
|
||||
<button id="af" role="menuitem" class="menu-bar-link">Afrikaans</button>
|
||||
<button id="zh" role="menuitem" class="menu-bar-link">Chinese</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if print_enable}}
|
||||
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
|
||||
|
||||
@@ -19,13 +19,11 @@
|
||||
try {
|
||||
const response = await fetch(url, { method: "GET" })
|
||||
if (!response.ok) {
|
||||
console.log(response)
|
||||
throw new Error(`Response status: ${response.status}`)
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
var sponsor = json.sponsor
|
||||
console.log("boop", sponsor)
|
||||
sponsorImg.src = sponsor.image_url
|
||||
sponsorTitle.textContent = sponsor.name
|
||||
sponsorDescription.innerHTML = sponsor.description
|
||||
|
||||
Reference in New Issue
Block a user