DNSSEC s BIND 9.9 snadno a rychle

23. 3. 2016
Doba čtení: 6 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Nasazení DNSSEC na autoritativní server je asi nejsložitější částí přechodu na DNSSEC. Není to ale nic, čeho by bylo třeba se obávat. Podepíšeme si zónu na DNS serveru BIND 9.9 s minimálními zásahy do infrastruktury.

Podpora automatického DNSSEC podepisování byla v serveru BIND přitomna již dříve, bylo ale vždy nutné přepnout zónu do dynamického režimu, což s sebou mohlo nést některé problémy. Ve verzi 9.9 se ale poprvé objevila funkce zvaná inline signing, která umožňuje zapnout podepisování a přitom zachovat původní zónové soubory beze změn.

Výchozí situace

Nasazení si prakticky předvedeme na Debianu Jessie, který obsahuje balíček bind9 ve verzi 9.9.5. Přepokládejme, že server slouží jako autoritativní server pro zónu example.com, která používá obyčejný textový zónový soubor umístěný v /etc/bind/example.com. V konfiguračním souboru /etc/bind/named.conf.local je pak následující definice zóny:

zone "example.com" {
        type master;
        file "/etc/bind/example.com";
}; 

Krok 0: dostatek entropie

Ještě než začneme zónu podepisovat, je dobré se ujistit, že systém bude mít k dispozici dostatek entropie, aby mohl generovat náhodná čísla skutečně náhodně. Všechny dále zmíněné nástroje používají jako zdroj náhodných čísel zařízení /dev/random, které při nedostatku entropie proces vytváření klíčů nebo podpisů blokuje až do chvíle, kdy entropie naroste zpět. Množství dat, které zařízení /dev/random vyrábí, můžeme ověřit nástrojem pv, který vypisuje každou sekundu počet bajtů, které protekly rourou:

# pv -nb /dev/random > /dev/null
192
192
192
^C 

Opakující se hodnota značí vyčerpanou zásobu entropie. Ve většině případů si můžeme pomoci instalací démona haveged, který získává další entropii z nejrůznějších vnitřních stavů procesoru:

# apt-get install haveged
# pv -nb /dev/random > /dev/null
1586646
3219499
4800458
^C 

Krok 1: vygenerujeme klíč

Automatické podepisování v BINDu dokáže generovat a obnovovat podpisy, klíče je však třeba generovat a případně měnit ručně. Abychom snížili míru ruční práce na minimum, použijeme k podpisu zóny pouze jediný klíč s algoritmem ECDSA P-256 a hashovací funkcí SHA256. Síla tohoto algoritmu by měla být větší než síla RSA klíče o délce 2048 bitů, takže jej není nutné vyměňovat dříve než za několik let. Zároveň jsou podpisy tímto algoritmem tak krátké, že si můžeme dovolit použít jej k podepisování každého záznamu zónového souboru.

# mkdir /etc/bind/keys
# cd /etc/bind/keys
# dnssec-keygen -a ECDSAP256SHA256 -fK example.com
Generating key pair.
Kexample.com.+013+32462
# chmod g+r K*.private 

V adresáři /etc/bind/keys vzniknou soubory s veřejným i privátním klíčem. Druhý jmenovaný má nastavena práva na 0600, takže je čitelný a zapisovatelný jen pro vlastníka, což je uživatel root. Aby jej mohl použít BIND k podepisování, je třeba práva upravit tak, aby daný soubor mohl číst. Tuto úpravu je třeba opakovat po každém generování klíče.

Krok 2: aktivace inline signingu

Nyní je již vše připraveno k aktivaci podepisování. Přitom se BIND pokusí vedle zónového souboru vytvořit soubory se žurnálem a s podepsanou verzí zóny. Je-li zónový soubor v /etc, toto se nepovede kvůli nedostatečnému oprávnění. Navíc z praktických důvodů není vhodné, aby se v konfiguračním adresáři objevovaly soubory automaticky generované a spravované. Vhodným řešením je přesunutí cesty k zónovému souboru z /etc  do provozního adresáře /var/cache/bind, kam má BIND právo zapisovat. Vytvoříme tedy symbolický odkaz na zónový soubor:

# ln -s /etc/bind/example.com /var/cache/bind 

Nyní již můžeme aktivovat podepisování úpravou konfigurace:

zone "example.com" {
        type master;
        file "example.com";
        inline-signing yes;
        auto-dnssec maintain;
        key-directory "/etc/bind/keys";
}; 

Po reloadu příkazem rndc reload by mělo dojít během několika okamžiků k podepsání zóny. Průběh můžeme sledovat pomocí:

# rndc signing -list example.com
Done signing with key 32462/ECDSAP256SHA256 

Standardně je podepisováno v režimu NSEC, kdy jsou všechna jména v zónovém souboru provázána a je tedy možné postupným dotazováním zjistit všechna existující jména. Pro situace, kde je toto problém, je možné přejít na hashované záznamy NSEC3:

# rndc signing -nsec3param 1 0 10 deadbeef example.com 

Číselné parametry určují postupně hashovací algoritmus NSEC3 (jediný definovaný je 1 – SHA256), flagy (žádné), počet iterací hashování (10 je rozumná hodnota) a konečně sůl, která zabraňuje použítí tzv. rainbow tabulek. Jde o libovolné šestnáctkové číslo dlouhé až 255 bytů. Důrazně zde doporučuji odolat pokušení k použití zprofanovaných slov jako cafebabe, deadbeef nebo  faceb00c.

Podepsaná zóna je uložena v provozním adresáři v souboru s příponou .signed. Kromě toho se v daném adresáři ještě nachází žurnálový soubor sledující změny v původním nepodepsaném souboru a žurnálový soubor podepsaného souboru. Podepsaný soubor je v surovém formátu, pokud jej chceme prozkoumat, je třeba použít utilitu  named-compilezone:

# named-compilezone -f raw -j -o - example.com /var/cache/bind/example.com.signed
zone example.com/IN: loaded serial 9 (DNSSEC signed)
example.com. 60 IN SOA ns.example.com. hostmaster.example.com. 9 120 10 3600 60
…
OK 

Změny DNS dat provádíme stejně jako před zavedením DNSSECu – editací nepodepsaného zónového souboru a reloadem serveru. Důležité je při každé změně zvýšit sériové číslo zóny v SOA záznamu, jinak ji BIND odmítne načíst. Sériové číslo podepsané zóny sleduje číslo nepodepsané zóny, automaticky je ale zvyšováno při obnovování podpisů nebo manipulaci s klíči.

Krok 3: sdělení nadřazené zóně

Posledním krokem k vytvoření řetěžu důvěry je umístění otisku klíče do nadřazené zóny. K tomu slouží utilitka  dnssec-dsfromkey:

# dnssec-dsfromkey /etc/bind/keys/Kexample.com.+013+32462
example.com. IN DS 32462 13 1 5E6C8…9D20C8
example.com. IN DS 32462 13 2 AB779…8A9001875886A0FF9170A2579AC9E1AB 

Vygenerované otisky se liší pouze algoritmem hashovací funkce, která je u kratšího SHA1, u delšího SHA256. Není třeba umisťovat do nadřazené zóny oba, zcela postačuje ten druhý, bezpečnější.

Speciálním případem jsou registry domén .cz a .eu. Tyto registry nepřijímají DS záznamy, ale rovnou celé veřejné klíče. Důvod je ten, že DS záznam je otiskem jak veřejného klíče, tak i doménového jména. Registry .cz a .eu pracují s tzv. keysety, které mohou být přiřazeny k většímu množství doménových jmen. Příslušné DS záznamy si pak registr vyrobí sám. Pro nás je důležité, že do keysetu vkládáme přímo obsah souboru s veřejným klíčem.

Krok 4: výměna klíče

Ačkoli je klíč daného algoritmu dostatečně silný, může nastat časem potřeba klíč vyměnit. Ani to není příliš obtížné, byť jistá ruční práce je vyžadována. Nejprve vygenerujeme nový klíč stejně jako v kroku 1 a sdělíme BINDu, že jej má používat:

# cd /etc/bind/keys/
# dnssec-keygen -a ECDSAP256SHA256 -fK example.com
Generating key pair.
Kexample.com.+013+11957
# chmod g+r *.private
# rndc sign example.com 

V tuto chvíli jsou aktivní oba klíče a můžeme tedy změnit DS záznam v nadřazené zóně tak, aby mířil na nový klíč. Po určité době, kdy se změna projeví a starý klíč se stane nepotřebným, můžeme naplánovat jeho deaktivaci (přestane být používán k vytváření podpisů) a poté jeho odstranění ze zóny:

zabbix_tip

# cd /etc/bind/keys
# dnssec-settime -I now -D +35d Kexample.com.+013+32462
dnssec-settime: warning: Permissions on the file
./Kexample.com.+013+32462.private have changed from 0640 to 0600 as a result of this operation.
./Kexample.com.+013+32462.key
./Kexample.com.+013+32462.private
# chmod g+r *.private
# rndc sign example.com 

Tímto je starý klíč okamžitě deaktivován a zcela odstraněn po 35 dnech – v této době by už zaručeně měly být všechny podpisy přegenerovány a klíč tedy bude bezpečné odebrat. To proběhne automaticky; BIND totiž adresář s klíči pravidelně kontroluje a s klíči nakládá podle časovacích metadat.

Závěrem

Podpora automatického podepisování s inline signingem umožňuje nasadit DNSSEC všude tam, kde je používán BIND. Nedochází zde k žádnému zásahu ani na straně vstupu, kde je obsluha stejná jako dříve, stejně jako není potřeba nijak upravovat případné slave servery, tedy za předpokladu, že používají přiměřeně aktuální software. Použití algoritmu ECDSA a jediného klíče celý postup zjednodušuje tak, že prakticky není potřeba věnovat žádnou péči navíc.

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »