Għamilt ir-repożitorju PyPI tiegħi stess b'awtorizzazzjoni u S3. Fuq Nginx

F'dan l-artikolu nixtieq naqsam l-esperjenza tiegħi ma 'NJS, interpretu JavaScript għal Nginx żviluppat minn Nginx Inc, li jiddeskrivi l-kapaċitajiet ewlenin tiegħu billi juża eżempju reali. NJS huwa subsett ta 'JavaScript li jippermettilek testendi l-funzjonalità ta' Nginx. Għall-mistoqsija ghalfejn l-interpretu tieghek??? Dmitry Volyntsev wieġeb fid-dettall. Fil-qosor: NJS huwa nginx-way, u JavaScript huwa aktar progressiv, "nattiv" u mingħajr GC, b'differenza Lua.

Żmien twil ilu…

Fl-aħħar xogħol tiegħi, wirt gitlab b'numru ta 'pipelines CI/CD differenti b'docker-compose, dind u delights oħra, li ġew trasferiti għal binarji kaniko. L-immaġini li kienu użati qabel fis-CI ġew imċaqalqa fil-forma oriġinali tagħhom. Ħadmu sew sal-ġurnata meta l-IP tal-gitlab tagħna nbidel u CI nbidel f'qargħa ħamra. Il-problema kienet li waħda mill-immaġini tad-docker li pparteċipaw f'CI kellha git, li ġibdet moduli Python permezz ta 'ssh. Għal ssh għandek bżonn ċavetta privata u... kienet fl-immaġni flimkien ma 'known_hosts. U kwalunkwe CI falla bi żball ta 'verifika ewlieni minħabba nuqqas ta' qbil bejn l-IP reali u dak speċifikat f'known_hosts. Immaġini ġdida ġiet immuntata malajr mill-Dockfiles eżistenti u l-għażla ġiet miżjuda StrictHostKeyChecking no. Iżda t-togħma ħażina baqgħet u kien hemm xewqa li l-libs jiġu mċaqalqa għal repożitorju PyPI privat. Bonus addizzjonali, wara li qaleb għal PyPI privat, kien pipeline aktar sempliċi u deskrizzjoni normali ta’ requirements.txt

L-għażla saret, Sinjuri!

Aħna nħaddmu kollox fis-sħab u Kubernetes, u fl-aħħar ridna niksbu servizz żgħir li kien kontenitur mingħajr stat b'ħażna esterna. Ukoll, peress li nużaw S3, ingħatatlu prijorità. U, jekk possibbli, b'awtentikazzjoni f'gitlab (tista' żżidha lilek innifsek jekk meħtieġ).

Tfittxija mgħaġġla tat diversi riżultati: s3pypi, pypicloud u għażla bil-ħolqien "manwali" ta 'fajls html għall-nevew. L-aħħar għażla sparixxa waħedha.

s3pypi: Dan huwa cli għall-użu S3 hosting. Aħna ntellgħu l-fajls, niġġeneraw l-html u ntellgħuh fl-istess barmil. Adattat għall-użu fid-dar.

pypicloud: Deher proġett interessanti, iżda wara li qrajt id-dokumentazzjoni kont diżappuntat. Minkejja dokumentazzjoni tajba u l-abbiltà li tespandi biex taqdi l-bżonnijiet tiegħek, fir-realtà rriżulta li kien żejjed u diffiċli biex jiġi kkonfigurat. Il-korrezzjoni tal-kodiċi biex jaqbel mal-kompiti tiegħek, skont l-istimi f'dak iż-żmien, kienet tieħu 3-5 ijiem. Is-servizz jeħtieġ ukoll database. Ħallieh f’każ li ma sibna xejn aktar.

Tfittxija aktar fil-fond tat modulu għal Nginx, ngx_aws_auth. Ir-riżultat tal-ittestjar tiegħu kien XML murija fil-browser, li wera l-kontenut tal-barmil S3. L-aħħar impenn fiż-żmien tat-tfittxija kien sena ilu. Ir-repożitorju deher abbandunat.

Billi tmur fis-sors u taqra PEP-503 Irrealizzajt li XML jista 'jiġi kkonvertit għal HTML fuq il-fly u jingħata lill-pip. Wara li googling ftit aktar dwar Nginx u S3, iltqajt ma 'eżempju ta' awtentikazzjoni f'S3 miktuba f'JS għal Nginx. Hekk iltqajt ma' NJS.

Meta nieħu dan l-eżempju bħala bażi, siegħa wara rajt fil-browser tiegħi l-istess XML bħal meta nuża l-modulu ngx_aws_auth, iżda kollox kien diġà miktub f'JS.

Għoġobni ħafna s-soluzzjoni nginx. L-ewwelnett, dokumentazzjoni tajba u ħafna eżempji, it-tieni nett, aħna nġibu l-goodies kollha ta 'Nginx biex naħdmu ma' fajls (barra mill-kaxxa), it-tielet, kull min jaf kif jikteb il-konfigurazzjonijiet għal Nginx ikun jista 'jifhem x'inhu dak. Il-minimaliżmu huwa wkoll plus għalija, meta mqabbel ma 'Python jew Go (jekk miktub mill-bidu), biex ma nsemmux nexus.

TL; DR Wara jumejn, il-verżjoni tat-test ta 'PyPi kienet diġà użata fis-CI.

Kif taħdem?

Il-modulu jitgħabba f'Nginx ngx_http_js_module, inkluż fl-immaġni uffiċjali tad-docker. Aħna jimportaw l-iskrittura tagħna bl-użu tad-direttiva js_importgħall-konfigurazzjoni Nginx. Il-funzjoni tissejjaħ minn direttiva js_content. Id-direttiva tintuża biex jiġu stabbiliti varjabbli js_set, li tieħu bħala argument biss il-funzjoni deskritta fl-iskrittura. Iżda nistgħu nwettqu subqueries f'NJS biss billi tuża Nginx, mhux xi XMLHttpRequest. Biex tagħmel dan, il-post korrispondenti għandu jiżdied mal-konfigurazzjoni Nginx. U l-iskrittura trid tiddeskrivi subtalba għal dan il-post. Biex tkun tista' taċċessa funzjoni mill-konfigurazzjoni Nginx, l-isem tal-funzjoni għandu jiġi esportat fl-iskritt innifsu export default.

nginx.conf

load_module modules/ngx_http_js_module.so;
http {
  js_import   imported_name  from script.js;

server {
  listen 8080;
  ...
  location = /sub-query {
    internal;

    proxy_pass http://upstream;
  }

  location / {
    js_content imported_name.request;
  }
}

script.js

function request(r) {
  function call_back(resp) {
    // handler's code
    r.return(resp.status, resp.responseBody);
  }

  r.subrequest('/sub-query', { method: r.method }, call_back);
}

export default {request}

Meta tintalab fil-browser http://localhost:8080/ nidħlu location /li fiha d-direttiva js_content jitlob funzjoni request deskritt fl-iskrittura tagħna script.js. Min-naħa tagħhom, fil-funzjoni request issir sottomistoqsija lil location = /sub-query, b'metodu (fl-eżempju attwali GET) miksub mill-argument (r), mgħoddi b'mod impliċitu meta din il-funzjoni tissejjaħ. Ir-rispons tas-subtalba se jiġi pproċessat fil-funzjoni call_back.

Jippruvaw S3

Biex tagħmel talba għal ħażna privata S3, għandna bżonn:

ACCESS_KEY

SECRET_KEY

S3_BUCKET

Mill-metodu http użat, id-data/ħin kurrenti, S3_NAME u URI, jiġi ġġenerat ċertu tip ta 'sekwenza, li hija ffirmata (HMAC_SHA1) bl-użu ta' SECRET_KEY. Li jmiss hija linja simili AWS $ACCESS_KEY:$HASH, jistgħu jintużaw fl-intestatura tal-awtorizzazzjoni. L-istess data/ħin li ntużat biex tiġġenera s-sekwenza fil-pass preċedenti trid tiġi miżjuda mal-header X-amz-date. Fil-kodiċi jidher bħal dan:

nginx.conf

load_module modules/ngx_http_js_module.so;
http {
  js_import   s3      from     s3.js;

  js_set      $s3_datetime     s3.date_now;
  js_set      $s3_auth         s3.s3_sign;

server {
  listen 8080;
  ...
  location ~* /s3-query/(?<s3_path>.*) {
    internal;

    proxy_set_header    X-amz-date     $s3_datetime;
    proxy_set_header    Authorization  $s3_auth;

    proxy_pass          $s3_endpoint/$s3_path;
  }

  location ~ "^/(?<prefix>[w-]*)[/]?(?<postfix>[w-.]*)$" {
    js_content s3.request;
  }
}

s3.js(Eżempju ta' awtorizzazzjoni AWS Sign v2, mibdul għal status deprekat)

var crypt = require('crypto');

var s3_bucket = process.env.S3_BUCKET;
var s3_access_key = process.env.S3_ACCESS_KEY;
var s3_secret_key = process.env.S3_SECRET_KEY;
var _datetime = new Date().toISOString().replace(/[:-]|.d{3}/g, '');

function date_now() {
  return _datetime
}

function s3_sign(r) {
  var s2s = r.method + 'nnnn';

  s2s += `x-amz-date:${date_now()}n`;
  s2s += '/' + s3_bucket;
  s2s += r.uri.endsWith('/') ? '/' : r.variables.s3_path;

  return `AWS ${s3_access_key}:${crypt.createHmac('sha1', s3_secret_key).update(s2s).digest('base64')}`;
}

function request(r) {
  var v = r.variables;

  function call_back(resp) {
    r.return(resp.status, resp.responseBody);
  }

  var _subrequest_uri = r.uri;
  if (r.uri === '/') {
    // root
    _subrequest_uri = '/?delimiter=/';

  } else if (v.prefix !== '' && v.postfix === '') {
    // directory
    var slash = v.prefix.endsWith('/') ? '' : '/';
    _subrequest_uri = '/?prefix=' + v.prefix + slash;
  }

  r.subrequest(`/s3-query${_subrequest_uri}`, { method: r.method }, call_back);
}

export default {request, s3_sign, date_now}

Ftit spjegazzjoni dwar _subrequest_uri: din hija varjabbli li, skont l-uri inizjali, tifforma talba lil S3. Jekk għandek bżonn tikseb il-kontenut tal-"għerq", allura trid toħloq talba uri li tindika d-delimitatur delimiter, li se jirritorna lista tal-elementi xml CommonPrefixes kollha, li jikkorrispondu għad-direttorji (fil-każ ta 'PyPI, lista tal-pakketti kollha). Jekk għandek bżonn tikseb lista ta 'kontenut f'direttorju speċifiku (lista tal-verżjonijiet kollha tal-pakkett), allura t-talba uri għandu jkun fiha qasam tal-prefiss bl-isem tad-direttorju (pakkett) neċessarjament jispiċċa b'linja mmejla /. Inkella, ħabtiet huma possibbli meta jintalab il-kontenut ta 'direttorju, pereżempju. Hemm direttorji aiohttp-request u aiohttp-requests u jekk it-talba tispeċifika /?prefix=aiohttp-request, allura r-rispons ikun fih il-kontenut taż-żewġ direttorji. Jekk ikun hemm mmejla fl-aħħar, /?prefix=aiohttp-request/, allura r-rispons ikun fih biss id-direttorju meħtieġ. U jekk nitolbu fajl, allura l-uri li tirriżulta m'għandhiex tkun differenti minn dik oriġinali.

Issejvja u erġa ibda Nginx. Fil-browser nidħlu l-indirizz ta 'Nginx tagħna, ir-riżultat tat-talba se jkun XML, pereżempju:

Lista ta' direttorji

<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>myback-space</Name>
  <Prefix></Prefix>
  <Marker></Marker>
  <MaxKeys>10000</MaxKeys>
  <Delimiter>/</Delimiter>
  <IsTruncated>false</IsTruncated>
  <CommonPrefixes>
    <Prefix>new/</Prefix>
  </CommonPrefixes>
  <CommonPrefixes>
    <Prefix>old/</Prefix>
  </CommonPrefixes>
</ListBucketResult>

Mil-lista tad-direttorji ser ikollok bżonn biss l-elementi CommonPrefixes.

Billi żżid id-direttorju li neħtieġu fl-indirizz tagħna fil-browser, aħna nirċievu wkoll il-kontenut tiegħu f'forma XML:

Lista ta' fajls f'direttorju

<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name> myback-space</Name>
  <Prefix>old/</Prefix>
  <Marker></Marker>
  <MaxKeys>10000</MaxKeys>
  <Delimiter></Delimiter>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>old/giphy.mp4</Key>
    <LastModified>2020-08-21T20:27:46.000Z</LastModified>
    <ETag>&#34;00000000000000000000000000000000-1&#34;</ETag>
    <Size>1350084</Size>
    <Owner>
      <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
      <DisplayName></DisplayName>
    </Owner>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>old/hsd-k8s.jpg</Key>
    <LastModified>2020-08-31T16:40:01.000Z</LastModified>
    <ETag>&#34;b2d76df4aeb4493c5456366748218093&#34;</ETag>
    <Size>93183</Size>
    <Owner>
      <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
      <DisplayName></DisplayName>
    </Owner>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

Mil-lista ta 'fajls se nieħdu elementi biss Key.

Kulma jibqa 'huwa li jiġi analizzat l-XML li jirriżulta u jibgħatu bħala HTML, wara li l-ewwel issostitwixxa l-header Content-Type b'test/html.

function request(r) {
  var v = r.variables;

  function call_back(resp) {
    var body = resp.responseBody;

    if (r.method !== 'PUT' && resp.status < 400 && v.postfix === '') {
      r.headersOut['Content-Type'] = "text/html; charset=utf-8";
      body = toHTML(body);
    }

    r.return(resp.status, body);
  }
  
  var _subrequest_uri = r.uri;
  ...
}

function toHTML(xml_str) {
  var keysMap = {
    'CommonPrefixes': 'Prefix',
    'Contents': 'Key',
  };

  var pattern = `<k>(?<v>.*?)</k>`;
  var out = [];

  for(var group_key in keysMap) {
    var reS;
    var reGroup = new RegExp(pattern.replace(/k/g, group_key), 'g');

    while(reS = reGroup.exec(xml_str)) {
      var data = new RegExp(pattern.replace(/k/g, keysMap[group_key]), 'g');
      var reValue = data.exec(reS);
      var a_text = '';

      if (group_key === 'CommonPrefixes') {
        a_text = reValue.groups.v.replace(///g, '');
      } else {
        a_text = reValue.groups.v.split('/').slice(-1);
      }

      out.push(`<a href="/mt/${reValue.groups.v}">${a_text}</a>`);
    }
  }

  return '<html><body>n' + out.join('</br>n') + 'n</html></body>'
}

Nipprova PyPI

Aħna niċċekkjaw li xejn ma jinkiser imkien fuq pakketti li huma magħrufa li jaħdmu.

# Создаем для тестов новое окружение
python3 -m venv venv
. ./venv/bin/activate

# Скачиваем рабочие пакеты.
pip download aiohttp

# Загружаем в приватную репу
for wheel in *.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done

rm -f *.whl

# Устанавливаем из приватной репы
pip install aiohttp -i http://localhost:8080

Nirrepetu bil-libs tagħna.

# Создаем для тестов новое окружение
python3 -m venv venv
. ./venv/bin/activate

pip install setuptools wheel
python setup.py bdist_wheel
for wheel in dist/*.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done

pip install our_pkg --extra-index-url http://localhost:8080

Fis-CI, il-ħolqien u t-tagħbija ta' pakkett jidher bħal dan:

pip install setuptools wheel
python setup.py bdist_wheel

curl -sSfT dist/*.whl -u "gitlab-ci-token:${CI_JOB_TOKEN}" "https://pypi.our-domain.com/${CI_PROJECT_NAME}"

Awtentikazzjoni

F'Gitlab huwa possibbli li tuża JWT għall-awtentikazzjoni/awtorizzazzjoni ta' servizzi esterni. Bl-użu tad-direttiva auth_request f'Nginx, aħna ser nidderieġu mill-ġdid id-dejta tal-awtentikazzjoni għal sottotalba li fiha sejħa ta 'funzjoni fl-iskritt. L-iskrittura se tagħmel sottotalba oħra lill-url tal-Gitlab u jekk id-dejta tal-awtentikazzjoni kienet speċifikata b'mod korrett, allura Gitlab jirritorna l-kodiċi 200 u l-upload/download tal-pakkett ikun permess. Għaliex ma tużax subquery waħda u immedjatament tibgħat id-dejta lil Gitlab? Għax imbagħad ikollna neditjaw il-fajl tal-konfigurazzjoni Nginx kull darba li nagħmlu xi tibdil fl-awtorizzazzjoni, u dan huwa kompitu pjuttost tedjanti. Ukoll, jekk Kubernetes juża politika ta 'sistema ta' fajls għerq ta 'qari biss, allura dan iżid saħansitra aktar kumplessità meta jissostitwixxi nginx.conf permezz ta' configmap. U jsir assolutament impossibbli li Nginx jiġi kkonfigurat permezz ta 'configmap filwaqt li fl-istess ħin tuża politiki li jipprojbixxu konnessjoni ta' volumi (pvc) u sistema ta 'fajls ta' għerq li jinqara biss (dan jiġri wkoll).

Bl-użu tal-NJS intermedju, ikollna l-opportunità li nbiddlu l-parametri speċifikati fil-konfigurazzjoni nginx billi tuża varjabbli tal-ambjent u nagħmlu xi kontrolli fl-iskrittura (per eżempju, URL speċifikat b'mod żbaljat).

nginx.conf

location = /auth-provider {
  internal;

  proxy_pass $auth_url;
}

location = /auth {
  internal;

  proxy_set_header Content-Length "";
  proxy_pass_request_body off;
  js_content auth.auth;
}

location ~ "^/(?<prefix>[w-]*)[/]?(?<postfix>[w-.]*)$" {
  auth_request /auth;

  js_content s3.request;
}

s3.js

var env = process.env;
var env_bool = new RegExp(/[Tt]rue|[Yy]es|[Oo]n|[TtYy]|1/);
var auth_disabled  = env_bool.test(env.DISABLE_AUTH);
var gitlab_url = env.AUTH_URL;

function url() {
  return `${gitlab_url}/jwt/auth?service=container_registry`
}

function auth(r) {
  if (auth_disabled) {
    r.return(202, '{"auth": "disabled"}');
    return null
  }

  r.subrequest('/auth-provider',
                {method: 'GET', body: ''},
                function(res) {
                  r.return(res.status, "");
                });
}

export default {auth, url}

X'aktarx il-mistoqsija hija l-birra: -Għaliex ma tużax moduli lesti? Kollox diġà sar hemm! Pereżempju, var AWS = require('aws-sdk') u m'hemmx bżonn li tikteb "rota" b'awtentikazzjoni S3!

Ejja ngħaddu għall-iżvantaġġi

Għalija, l-inkapaċità li timporta moduli JS esterni saret karatteristika spjaċevoli, iżda mistennija. Deskritt fl-eżempju hawn fuq require('crypto') is build-in moduli u jeħtieġu biss xogħlijiet għalihom. M'hemm l-ebda mod kif terġa 'tuża l-kodiċi mill-iskripts u trid tikkopja u tippejstjah f'fajls differenti. Nittama li xi darba din il-funzjonalità tiġi implimentata.

Il-kompressjoni għandha wkoll tkun diżattivata għall-proġett attwali f'Nginx gzip off;

Minħabba li m'hemm l-ebda modulu gzip fl-NJS u huwa impossibbli li tikkonnettjah; għalhekk, m'hemm l-ebda mod kif taħdem b'dejta kkompressata. Veru, dan mhux verament minus għal dan il-każ. M'hemmx ħafna test, u l-fajls trasferiti huma diġà kkompressati u kompressjoni addizzjonali mhux se tgħinhom ħafna. Barra minn hekk, dan mhuwiex servizz tant mgħobbi jew kritiku li għandek tolqot bil-kunsinna tal-kontenut ftit millisekondi aktar malajr.

Id-debugging tal-iskript jieħu ħafna żmien u huwa possibbli biss permezz ta '"prints" f'error.log. Skont l-informazzjoni tal-livell tal-illoggjar stabbilit, twissi jew żball, huwa possibbli li tuża 3 metodi r.log, r.warn, r.error rispettivament. Nipprova niddibaggja xi skripts fil-Chrome (v8) jew l-għodda tal-console njs, iżda mhux kollox jista 'jiġi ċċekkjat hemmhekk. Meta l-kodiċi tad-debugging, magħruf ukoll bħala ttestjar funzjonali, l-istorja tidher xi ħaġa bħal din:

docker-compose restart nginx
curl localhost:8080/
docker-compose logs --tail 10 nginx

u jista 'jkun hemm mijiet ta' sekwenzi bħal dawn.

Il-kitba tal-kodiċi bl-użu ta 'subqueries u varjabbli għalihom tinbidel f'tħabbil imħabbbil. Xi drabi tibda tgħaġġel madwar twieqi IDE differenti biex tipprova ssib is-sekwenza tal-azzjonijiet tal-kodiċi tiegħek. Mhuwiex diffiċli, imma xi drabi huwa tedjanti ħafna.

M'hemm l-ebda appoġġ sħiħ għall-ES6.

Jista 'jkun hemm xi nuqqasijiet oħra, imma jien ma ltqajt ma' xi ħaġa oħra. Aqsam l-informazzjoni jekk għandek esperjenza negattiva bl-użu tal-NJS.

Konklużjoni

NJS huwa interpretu ħafif open-source li jippermettilek timplimenta diversi skripts JavaScript f'Nginx. Matul l-iżvilupp tagħha, ingħatat attenzjoni kbira lill-prestazzjoni. Naturalment, għad hemm ħafna nieqes, iżda l-proġett qed jiġi żviluppat minn tim żgħir u qed iżidu b'mod attiv karatteristiċi ġodda u jiffissaw bugs. Nittama li xi darba NJS jippermettilek tikkonnettja moduli esterni, li jagħmlu l-funzjonalità ta 'Nginx kważi illimitata. Iżda hemm NGINX Plus u x'aktarx mhux se jkun hemm karatteristiċi!

Repożitorju bil-kodiċi sħiħ għall-artiklu

njs-pypi b'appoġġ AWS Sign v4

Deskrizzjoni tad-direttivi tal-modulu ngx_http_js_module

Repożitorju uffiċjali tal-NJS и dokumentazzjoni

Eżempji ta 'użu ta' NJS minn Dmitry Volintsev

njs - scripting nattiv ta' JavaScript f'nginx / Diskors minn Dmitry Volnyev f'Saint HighLoad++ 2019

NJS fil-produzzjoni / Diskors minn Vasily Soshnikov f'HighLoad++ 2019

Iffirmar u Awtentikazzjoni ta' Talbiet REST fl-AWS

Sors: www.habr.com